From 16db45f529c5699270674d2c093f147c6e0af555 Mon Sep 17 00:00:00 2001 From: Pekka Helenius Date: Wed, 30 Sep 2020 19:03:21 +0300 Subject: [PATCH] Allow more flexibility when adding book authors Signed-off-by: Pekka Helenius --- .../com/fjordtek/bookstore/model/Author.java | 47 ++++++---- .../com/fjordtek/bookstore/model/Book.java | 7 ++ .../bookstore/web/BookAuthorHelper.java | 86 +++++++++++-------- .../web/BookBasePathAwareController.java | 9 +- .../bookstore/web/BookController.java | 19 +++- 5 files changed, 109 insertions(+), 59 deletions(-) diff --git a/bookstore/src/main/java/com/fjordtek/bookstore/model/Author.java b/bookstore/src/main/java/com/fjordtek/bookstore/model/Author.java index 892c09a..13cf360 100644 --- a/bookstore/src/main/java/com/fjordtek/bookstore/model/Author.java +++ b/bookstore/src/main/java/com/fjordtek/bookstore/model/Author.java @@ -13,7 +13,6 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.SequenceGenerator; -import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; @@ -33,9 +32,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; @Entity public class Author { - private static final int strMin = 2; private static final int strMax = 100; - // We format length check in Size annotation, not here + + // We format length check in Size annotations, not here private static final String regexCommon = "^[a-zA-Z0-9\\-:\\s]*$"; //////////////////// @@ -56,16 +55,16 @@ public class Author { ////////// @Column( name = "firstname", - nullable = false, + nullable = true, columnDefinition = "NVARCHAR(" + strMax + ")" ) @Size( - min = strMin, max = strMax, - message = "First name length must be " + strMin + "-" + strMax + " characters" + max = strMax, + message = "First name length must be " + strMax + " characters at most" ) - @NotBlank( + /*@NotBlank( message = "Fill the first name form" - ) + )*/ @Pattern( regexp = regexCommon, message = "Invalid characters" @@ -78,16 +77,16 @@ public class Author { ////////// @Column( name = "lastname", - nullable = false, + nullable = true, columnDefinition = "NVARCHAR(" + strMax + ")" ) @Size( - min = strMin, max = strMax, - message = "Last name length must be " + strMin + "-" + strMax + " characters" + max = strMax, + message = "Last name length must be " + strMax + " characters at most" ) - @NotBlank( + /*@NotBlank( message = "Fill the first name form" - ) + )*/ @Pattern( regexp = regexCommon, message = "Invalid characters" @@ -118,13 +117,27 @@ public class Author { } public void setFirstName(String firstName) { - // Delete leading & trailing whitespaces (typos from user) - this.firstName = firstName.trim(); + + // Delete leading & trailing white spaces + firstName = firstName.trim(); + + if (firstName.isEmpty()) { + this.firstName = null; + } else { + this.firstName = firstName; + } } public void setLastName(String lastName) { - // Delete leading & trailing whitespaces (typos from user) - this.lastName = lastName.trim(); + + // Delete leading & trailing white spaces + lastName = lastName.trim(); + + if (lastName.isEmpty()) { + this.lastName = null; + } else { + this.lastName = lastName; + } } public void setBooks(List books) { 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 e293934..88bb104 100644 --- a/bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java +++ b/bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java @@ -20,6 +20,7 @@ import javax.persistence.ManyToOne; import javax.persistence.OneToOne; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.SequenceGenerator; +import javax.validation.Valid; import javax.validation.constraints.DecimalMax; import javax.validation.constraints.DecimalMin; import javax.validation.constraints.Digits; @@ -144,6 +145,12 @@ public class Book { name = "author_id", nullable = true ) + /* + * Trigger nested (web form) constraint validation when + * this entity object is referred from another entity + * object. + */ + @Valid private Author author; ////////// diff --git a/bookstore/src/main/java/com/fjordtek/bookstore/web/BookAuthorHelper.java b/bookstore/src/main/java/com/fjordtek/bookstore/web/BookAuthorHelper.java index 2b74a11..1e70486 100644 --- a/bookstore/src/main/java/com/fjordtek/bookstore/web/BookAuthorHelper.java +++ b/bookstore/src/main/java/com/fjordtek/bookstore/web/BookAuthorHelper.java @@ -27,61 +27,79 @@ public class BookAuthorHelper { @Autowired private AuthorRepository authorRepository; - public void detectAndSaveUpdateAuthorForBook(Book book) { + public Author detectAndSaveUpdateAuthorByName(String firstName, String lastName) { /* - * Find an author from the current AUTHOR table by his/her first and last name, - * included in the Book entity object. In CrudRepository, if a matching Id - * value is not found, it is stored as a new Author entity object. Therefore, - * it's crucial to identify whether author row values already exist in - * AUTHOR table. + * Find an author from the current AUTHOR table by his/her first and/or last + * name. In CrudRepository, if a matching Id value is not found, it is stored + * as a new Author entity object. Therefore, it's crucial to identify whether + * author row values already exist in AUTHOR table. */ + Author author = null; + try { - Author authorI = authorRepository.findByFirstNameIgnoreCaseContainingAndLastNameIgnoreCaseContaining( - book.getAuthor().getFirstName(),book.getAuthor().getLastName()) - .get(0); /* - * When author is found, use it's Id attribute for book's author Id - * - * NOTE: This step is required by Hibernate, otherwise we get - * TransientPropertyValueException + * Find by first and last name; ignore case */ - book.getAuthor().setId(authorI.getId()); + if (firstName != null && lastName != null) { + author = authorRepository.findByFirstNameIgnoreCaseContainingAndLastNameIgnoreCaseContaining( + firstName,lastName).get(0); + } /* - * Otherwise, consider this a new author and store it appropriately. - * Actually, when author is not found, we get IndexOutOfBoundsException. + * Find by first name; ignore case + * NOTE: There is a risk we get a wrong Author! + * Should we auto-detect author or let the user absolutely + * decide the naming scheme without 'intelligent' detection feature? */ + if (firstName != null && lastName == null) { + author = authorRepository.findByFirstNameIgnoreCaseContaining( + firstName).get(0); + } + + /* + * Find by last name; ignore case + * NOTE: There is a risk we get a wrong Author! + * Should we auto-detect author or let the user absolutely + * decide the naming scheme without 'intelligent' detection feature? + */ + if (firstName == null && lastName != null) { + author = authorRepository.findByLastNameIgnoreCaseContaining( + lastName).get(0); + } + + /* + * If author is still not found, we get IndexOutOfBoundsException + * Consider this a new author and store it appropriately. + */ } catch (IndexOutOfBoundsException e) { - authorRepository.save(book.getAuthor()); + author = authorRepository.save(new Author(firstName, lastName)); } + return author; } - public Author detectAndSaveAuthorByName(String firstName, String lastName) { + public void detectAndSaveUpdateAuthorForBook(Book book) { + + Author author = this.detectAndSaveUpdateAuthorByName( + book.getAuthor().getFirstName(), + book.getAuthor().getLastName() + ); /* - * Find an author from the current AUTHOR table by his/her first and last name. - * In CrudRepository, if a matching Id value is not found, it is stored - * as a new Author entity object. Therefore, it's crucial to identify - * whether author row values already exist in AUTHOR table. + * When author is found, use it's Id attribute for book's author Id + * + * NOTE: This step is required by Hibernate, + * otherwise we get TransientPropertyValueException */ - - try { - return authorRepository.findByFirstNameIgnoreCaseContainingAndLastNameIgnoreCaseContaining( - firstName,lastName).get(0); - /* - * Otherwise, consider this a new author and store it appropriately. - * Actually, when author is not found, we get IndexOutOfBoundsException. - */ - } catch (IndexOutOfBoundsException e) { - return authorRepository.save(new Author(firstName,lastName)); + if (author != null) { + book.getAuthor().setId(author.getId()); + } else { + book.setAuthor(null); } } - // TODO implement methods to save author even if either his/her first or last name is missing - } \ No newline at end of file diff --git a/bookstore/src/main/java/com/fjordtek/bookstore/web/BookBasePathAwareController.java b/bookstore/src/main/java/com/fjordtek/bookstore/web/BookBasePathAwareController.java index 19c99d3..3703091 100644 --- a/bookstore/src/main/java/com/fjordtek/bookstore/web/BookBasePathAwareController.java +++ b/bookstore/src/main/java/com/fjordtek/bookstore/web/BookBasePathAwareController.java @@ -86,11 +86,10 @@ public class BookBasePathAwareController { book.setAuthor(null); book.setCategory(null); - if (authorFirstName != null && authorLastName != null) { - book.setAuthor( - bookAuthorHelper.detectAndSaveAuthorByName(authorFirstName, authorLastName) - ); - } + book.setAuthor( + bookAuthorHelper.detectAndSaveUpdateAuthorByName(authorFirstName, authorLastName) + ); + if (categoryName != null) { book.setCategory( categoryRepository.findByNameIgnoreCaseContaining(categoryName).get(0) 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 f16ad1a..d3ed06a 100644 --- a/bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java +++ b/bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java @@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; @@ -176,8 +177,6 @@ public class BookController { httpServerLogger.log(requestData, responseData); - bookAuthorHelper.detectAndSaveUpdateAuthorForBook(book); - /* * Generate hash id for the book. One-to-one unidirectional tables. * Associate generated book hash object information @@ -189,10 +188,17 @@ public class BookController { book.setBookHash(bookHash); bookHash.setBook(book); + /* + * More sophisticated methods are required to handle + * user input with random letter cases etc. considered + */ + //authorRepository.save(book.getAuthor()); + bookAuthorHelper.detectAndSaveUpdateAuthorForBook(book); + bookRepository.save(book); bookHashRepository.save(bookHash); - return "redirect:" + bookListPageView; + return "redirect:/" + bookListPageView; } ////////////////////////////// @@ -269,6 +275,7 @@ public class BookController { value = bookEditPageView + "/{hash_id}", method = RequestMethod.POST ) + @ExceptionHandler public String webFormUpdateBook( @Valid @ModelAttribute("book") Book book, BindingResult bindingResultBook, @@ -326,7 +333,13 @@ public class BookController { return bookEditPageView; } + /* + * More sophisticated methods are required to handle + * user input with random letter cases etc. considered + */ + //authorRepository.save(book.getAuthor()); bookAuthorHelper.detectAndSaveUpdateAuthorForBook(book); + bookRepository.save(book); httpServerLogger.log(requestData, responseData);