Browse Source

Allow more flexibility when adding book authors

Signed-off-by: Pekka Helenius <fincer89@hotmail.com>
v0.0.2-alpha
Pekka Helenius 4 years ago
parent
commit
16db45f529
5 changed files with 109 additions and 59 deletions
  1. +30
    -17
      bookstore/src/main/java/com/fjordtek/bookstore/model/Author.java
  2. +7
    -0
      bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java
  3. +52
    -34
      bookstore/src/main/java/com/fjordtek/bookstore/web/BookAuthorHelper.java
  4. +4
    -5
      bookstore/src/main/java/com/fjordtek/bookstore/web/BookBasePathAwareController.java
  5. +16
    -3
      bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java

+ 30
- 17
bookstore/src/main/java/com/fjordtek/bookstore/model/Author.java View File

@ -13,7 +13,6 @@ import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator; import javax.persistence.SequenceGenerator;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern; import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
@ -33,9 +32,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
@Entity @Entity
public class Author { public class Author {
private static final int strMin = 2;
private static final int strMax = 100; 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]*$"; private static final String regexCommon = "^[a-zA-Z0-9\\-:\\s]*$";
//////////////////// ////////////////////
@ -56,16 +55,16 @@ public class Author {
////////// //////////
@Column( @Column(
name = "firstname", name = "firstname",
nullable = false,
nullable = true,
columnDefinition = "NVARCHAR(" + strMax + ")" columnDefinition = "NVARCHAR(" + strMax + ")"
) )
@Size( @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" message = "Fill the first name form"
)
)*/
@Pattern( @Pattern(
regexp = regexCommon, regexp = regexCommon,
message = "Invalid characters" message = "Invalid characters"
@ -78,16 +77,16 @@ public class Author {
////////// //////////
@Column( @Column(
name = "lastname", name = "lastname",
nullable = false,
nullable = true,
columnDefinition = "NVARCHAR(" + strMax + ")" columnDefinition = "NVARCHAR(" + strMax + ")"
) )
@Size( @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" message = "Fill the first name form"
)
)*/
@Pattern( @Pattern(
regexp = regexCommon, regexp = regexCommon,
message = "Invalid characters" message = "Invalid characters"
@ -118,13 +117,27 @@ public class Author {
} }
public void setFirstName(String firstName) { 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) { 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<Book> books) { public void setBooks(List<Book> books) {


+ 7
- 0
bookstore/src/main/java/com/fjordtek/bookstore/model/Book.java View File

@ -20,6 +20,7 @@ import javax.persistence.ManyToOne;
import javax.persistence.OneToOne; import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.SequenceGenerator; import javax.persistence.SequenceGenerator;
import javax.validation.Valid;
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.Digits;
@ -144,6 +145,12 @@ public class Book {
name = "author_id", name = "author_id",
nullable = true nullable = true
) )
/*
* Trigger nested (web form) constraint validation when
* this entity object is referred from another entity
* object.
*/
@Valid
private Author author; private Author author;
////////// //////////


+ 52
- 34
bookstore/src/main/java/com/fjordtek/bookstore/web/BookAuthorHelper.java View File

@ -27,61 +27,79 @@ public class BookAuthorHelper {
@Autowired @Autowired
private AuthorRepository authorRepository; 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 { 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) { } 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
} }

+ 4
- 5
bookstore/src/main/java/com/fjordtek/bookstore/web/BookBasePathAwareController.java View File

@ -86,11 +86,10 @@ public class BookBasePathAwareController {
book.setAuthor(null); book.setAuthor(null);
book.setCategory(null); book.setCategory(null);
if (authorFirstName != null && authorLastName != null) {
book.setAuthor(
bookAuthorHelper.detectAndSaveAuthorByName(authorFirstName, authorLastName)
);
}
book.setAuthor(
bookAuthorHelper.detectAndSaveUpdateAuthorByName(authorFirstName, authorLastName)
);
if (categoryName != null) { if (categoryName != null) {
book.setCategory( book.setCategory(
categoryRepository.findByNameIgnoreCaseContaining(categoryName).get(0) categoryRepository.findByNameIgnoreCaseContaining(categoryName).get(0)


+ 16
- 3
bookstore/src/main/java/com/fjordtek/bookstore/web/BookController.java View File

@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder; 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.InitBinder;
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;
@ -176,8 +177,6 @@ public class BookController {
httpServerLogger.log(requestData, responseData); httpServerLogger.log(requestData, responseData);
bookAuthorHelper.detectAndSaveUpdateAuthorForBook(book);
/* /*
* Generate hash id for the book. One-to-one unidirectional tables. * Generate hash id for the book. One-to-one unidirectional tables.
* Associate generated book hash object information * Associate generated book hash object information
@ -189,10 +188,17 @@ public class BookController {
book.setBookHash(bookHash); book.setBookHash(bookHash);
bookHash.setBook(book); 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); bookRepository.save(book);
bookHashRepository.save(bookHash); bookHashRepository.save(bookHash);
return "redirect:" + bookListPageView;
return "redirect:/" + bookListPageView;
} }
////////////////////////////// //////////////////////////////
@ -269,6 +275,7 @@ public class BookController {
value = bookEditPageView + "/{hash_id}", value = bookEditPageView + "/{hash_id}",
method = RequestMethod.POST method = RequestMethod.POST
) )
@ExceptionHandler
public String webFormUpdateBook( public String webFormUpdateBook(
@Valid @ModelAttribute("book") Book book, @Valid @ModelAttribute("book") Book book,
BindingResult bindingResultBook, BindingResult bindingResultBook,
@ -326,7 +333,13 @@ public class BookController {
return bookEditPageView; return bookEditPageView;
} }
/*
* More sophisticated methods are required to handle
* user input with random letter cases etc. considered
*/
//authorRepository.save(book.getAuthor());
bookAuthorHelper.detectAndSaveUpdateAuthorForBook(book); bookAuthorHelper.detectAndSaveUpdateAuthorForBook(book);
bookRepository.save(book); bookRepository.save(book);
httpServerLogger.log(requestData, responseData); httpServerLogger.log(requestData, responseData);


Loading…
Cancel
Save