How can I validate a complex model object in Spring 3 using #Valid annotations? - validation

I have a model object modelling a purchase order. The purchase order has a few fields (such as ID and date) and a list of line-items as ArrayList. I can validate the parent purchase order ok, but it chokes when validating the line-items.
Can anyone help me with validation of complex objects? If I cannot validate complex objects auto-magically, how can I write a custom validator that relies upon the constraint annotations in the parent and then iterates over the child line-items? This Validator instance needs to be able to call something.validate(purchaseOrder) and (for each line-item) something.validate(lineItem). Where do I get "something" from?
I have specified <mvc:annotation-driven /> in dispatcher-servlet. I am not using #InitBinder. And I am using #Valid annotation for validation in controller's method like
#RequestMapping(params="confirmPurchaseOrder")
public String confirm(
#ModelAttribute("purchaseOrder") #Valid PurchaseOrder purchaseOrder,
BindingResult result,
#RequestParam("account") String accountString,
#RequestParam("division") String divisionString,
Model model)
{
if (result.hasErrors()) {
return PURCHASE_ORDER_CREATE_VIEW;
}
The domain classes look like this: -
public class PurchaseOrder implements Comparable<PurchaseOrder> {
/** Based on GUID */
private String id;
/** SOP */
#NotNull
private Integer SOP;
/** Reference from client */
#NotBlank
private String purchaseOrderReference;
/** PO date */
#DateTimeFormat(style="S-")
#NotNull
private Date date;
#Valid
private final Collection<LineItem> lineItems = new ArrayList<LineItem>();
And
public class LineItem {
...Elided
/** Generated from GUID */
private String id;
#NotNull
#DateTimeFormat(style="S-")
#Future
private Date expiry;
private String softwareVersion;
#NotNull
#NumberFormat(style = Style.NUMBER)
#Min(1)
private Integer licenceCount;
When committing a Purchase Order with an empty expiry date, I get the following exception:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'lineItems[]' of bean class [com.nit.ols.domain.PurchaseOrder]: Invalid index in property path 'lineItems[]'; nested exception is java.lang.NumberFormatException: For input string: ""
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:730)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:634)
javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
javax.servlet.http.HttpServlet.service(HttpServlet.java:722)

In your PurchaseOrder class, try changing your lineItems collection to a List. It looks like you are having the same problem addressed in this question.

It's like David said, declaring lineItems of type List should do the trick. In Hibernate Validator 4.2.0 CR1 (not yet released atm, you could use the latest snapshot build if you are interested) also Collection should work, see HV-468 for more details.

Related

Not able to search data in redis cache using spring crud repository by passing list of values for a property of the model saved in cache

We have model class saved in Redis as mentioned below:-
#Data
#NoArgsConstructor
#AllArgsConstructor
#RedisHash("book")
public class Book implements Serializable {
private static final long serialVersionUID = 2208852329346517265L;
#Id
private Integer bookID;
#Indexed
private String title;
#Indexed
private String authors;
private String averageRating;
private String isbn;
private String languageCode;
private String ratingsCount;
private BigDecimal price;
}
We have title and authors as our indexed property.
Now we wanted to search all the records from Redis by passing title and a list of authors using the spring crud repository as mentioned below.
public interface BookSpringRepository extends CrudRepository<Book, String> {
List<Book> findAllByTitleAndAuthors(String title, List<String> authors);
}
Service layer:-
#Override
public Optional<List<Book>> searchBooksByTitleAndAuthorNames(String title, List<String>
autherNames) {
return Optional.ofNullable(bookSpringRepository.findAllByTitleAndAuthors(title,
autherNames));
}
Here we are getting below exception
Unable to fetch data from Spring data Redis cache using List of Integer or
String.
Getting error while fetching - "Resolved
[org.springframework.core.convert.ConversionFailedException: Failed to convert from type
[java.lang.String] to type [byte] for value 'Ronak';
nested exception is java.lang.NumberFormatException: For input string: "Ronak"]."
We would not want to convert the list of string/integer to byte as it is a time-consuming process and as we tried took so much amount of time. Also when the results are retrieved we will again have to convert back to normal integer or string values.
The other option is to loop through the list and pass a single value at a time to the Redis crud repository and this time Redis crud repository is happy but that will be a loop call to Redis and network latency.
We cannot add ID attributes on authors' property as these can be duplicate records.
Does the spring crud repository support the LIKE query in search that way we can create a unique id having these authors' names and make put ID annotation on that new derived property to search the records using spring crud repository using LIKE or contains kind of query.
Any suggestions here are highly appreciated!!
Try to add serialization to your redis key and value. This might help :
https://medium.com/#betul5634/redis-serialization-with-spring-redis-data-lettuce-codec-1a1d2bc73d26

How do I get Spring's Data Rest Repository to retrieve data by its name instead of its id

I am using Spring Data's Rest Repositories from spring-boot-starter-data-rest, with Couchbase being used as the underlining DBMS.
My Pojo for the object is setup as so.
#Document
public class Item{
#Id #GeneratedValue(strategy = UNIQUE)
private String id;
#NotNull
private String name;
//other items and getters and setters here
}
And say the Item has an id of "xxx-xxx-xxx-xxx" and name of "testItem".
Problem is, that when I want to access the item, I need to be accessible by /items/testItem, but instead it is accessible by /items/xxx-xxx-xxx-xxx.
How do I get use its name instead of its generated id, to get the data.
I found out the answer to my own question.
I just need to override the config for the EntityLookup.
#Component
public class SpringDataRestCustomization extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.withEntityLookup().forRepository(UserRepository.class).
withIdMapping(User::getUsername).
withLookup(UserRepository::findByUsername);
}
}
Found the info here, though the method name changed slightly.
https://github.com/spring-projects/spring-data-examples/tree/master/rest/uri-customization
If you want query the item by name and want it perform as querying by id,you should make sure the name is unique too.You cant identify a explicit object by name if all objects have a same name,right?
With jpa you could do it like:
#NotNull
#Column(name="name",nullable=false,unique=true)
private String name;

How to use #RestController (Spring) with a child List of object

I'm trying to create a REST service with Spring.
Everything works until I try to add a List of object (CartItem) to my main object (Cart).
This is my main object
#Entity
#Table(name="cart")
public class Cart implements Serializable{
...
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Id
#Column(name="id")
private Integer id;
/*when I add this I get the error. If I remove this, the
REST service works*/
#OneToMany(mappedBy="cart", fetch = FetchType.EAGER)
private List<CartItem> cartItems;
//getter, setter, constructors, other fields ecc.
}
This is the object inside the List:
#Entity
#Table(name="cart_item")
public class CartItem implements Serializable{
...
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Id
#Column(name="id")
private Integer id;
#OneToOne(targetEntity = Product.class, cascade = CascadeType.ALL)
#JoinColumn(referencedColumnName="productId", name="product_id" )
private Product product;
#ManyToOne
#JoinColumn(name="cart_id", nullable=false)
private Cart cart;
//getter, setter, constructors, other fields ecc.
}
This is my controller
#RestController
#RequestMapping(value="rest/cart")
public class CartRestController {
...
#RequestMapping(value = "/", method = RequestMethod.GET)
public List<Cart> readAll() {
return cartService.read();
}
...
}
I get this error:
SEVERE: Servlet.service() for servlet [dispatcher] in context with path
[/webstore] threw exception [Request processing failed; nested exception
is org.springframework.http.converter.HttpMessageNotWritableException:
Could not write JSON: Infinite recursion (StackOverflowError); nested
exception is com.fasterxml.jackson.databind.JsonMappingException:
Infinite recursion (StackOverflowError) (through reference chain:...
I suppose that I had to manage the List inside the Cart object in a particular manner, maybe because i'm using JPA, but I still didn't find a solution on the internet.
Can anyone help me?
This is a serialization recursion problem, it happens because CartItem has a bidirectional mapping back to Cart. So what happens is that
a Cart gets serialized to JSON
all the CartItems inside it get serialized to JSON
the Cart property inside CartItem get serialized to JSON
the CartItems inside the cart get serialized to json, etc. etc.
You will probably want to exclude the CartItem.cart field from serialization by marking it with the #JsonIgnore annotation.
It is only too easy to expose far too much information to the outside world if you use JPA entities directly inside your webservices. Jackson actually has a useful feature called a JsonView which allows you to define which properties get exposed, you can even tailor it per webservice call if you want.
Never ending list? Did you mean a stackOverFlow exception?
If the situation is just like I said,then you should check something like fetch type and the entities' toString() or equal() method or something like that.
For example,there are to entities named A and B and their relationship is one to many(A is the one).If you config both of their fetchType as Eager,then when jpa query A,it will query B too.But B also contains A,so jpa will query A again.This kind of circle loop will cause a stackOverFlow.
By the way, how about providing more info about your problem like the Exception name?It's too hard for me to give you a specific solution,All I can do is to tell you some experiences I have met before.
Well,I created a small project with SpringBoot 2.1.0 and MySql.
It's my cartItem
public class CartItem {
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Id
#Column(name="id")
private Integer id;
#JsonIgnore
#ManyToOne
#JoinColumn(name="cart_id", nullable=false)
private Cart cart;
}
and my cart:
public class Cart {
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Id
#Column(name="id")
private Integer id;
#OneToMany(mappedBy="cart", fetch = FetchType.EAGER)
private List<CartItem> cartItems;
}
Controller is as same as you wrote.After adding a #JsonIgnore to cart filed of CartItem,circle loop is over(before i do that,the program did had a circle loop problem).
Every time you use jpa with #oneToMany,#ManyToOne or #ManyToMany,you should be careful about this problem.This circular reference case could happen when instantiating a object, printing a object or something like this.And of course there is a lot of way to solve it like changing fetch type to LAZY,adding #JsonIgnore,overriding toString() and equal() method.

Marking a field as mandatory in the request body for a specific request

I intend to use the same bean as request body for creating/updating/deleting profile (depending on request methods). There are two scenarios for an update. First is updating profile where any parameter like firstName, lastName etc. can be updated except the emailAddress and the second is updating only the emailAddress. For the first case newEmailAddress is optional but for the second case, newEmailAddress field is mandatory.
Below is a sample bean:
class ProfileModel {
#NotEmpty
#Email
private String emailAddress;
#Size(max=30)
private String firstName;
#Email
private String newEmailAddress;
.
.
.
}
I created different API endpoints for updating profile and updating the email address. I tried to find a way to make newEmailAddress field as optional for the first request and required for the second request but couldn't find anything but to manually check in the controller method for the second request and throw Exception.
Is there any other way through which this can be achieved?
The validation groups should solve your problem. See the examples either on beanvalidation.org or at hibernate-validator documentation page.
Basically you will need to add a group attribute value to your annotation constraints. Something like:
class ProfileModel {
#NotEmpty
#Email
private String emailAddress;
#Size(max=30)
private String firstName;
#Email(groups= UpdateEmail.class)
private String newEmailAddress;
and then on your controllers make use of Springs #Validated annotation which allows you to pass a group for which you want to validate.
One endpoint could use then this UpdateEmail group and the other the Default one.
This can be achieved using validation groups. That needs an identifier class or interface.
So you can do something like this:
=> Define a validation group identifier. It can be a class or interface.
public interface MyValidationGroup{}
=> Specify the validation group on request body.
class ProfileModel {
#NotEmpty
#Email
private String emailAddress;
#Size(max=30)
private String firstName;
#Email
#NotBlank(groups={MyValidationGroup.class})
private String newEmailAddress;
.
.
.
}
=> Specify validation group on controller method.
public ProfileModel add(#Validated({MyValidationGroup.class})
#RequestBody ProfileModel profile){
...
}

Spring: How do I construct this command object through a GET HTTP request?

I'm using Spring 3.1.0.RELEASE with Hibernate 4.0.1.Final. I want to invoke a search method in a controller that takes as input a search bean (the Event bean below) ...
#RequestMapping(value = "/search_results.jsp")
public ModelAndView processSearch(final HttpServletRequest request, final Event searchBean, final BindingResult result) {
...
}
The event bean contains the following field ...
#Entity
#Table(name = "EVENTS")
public class Event implements Comparable {
...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="EVENT_FEED_ID")
private EventFeed eventFeed;
...
}
in which the EventFeed object contains the following fields ...
#Entity
#Table(name = "EVENT_FEEDS")
public class EventFeed {
#Id
#Column(name = "ID")
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
#NotEmpty
#Column(name = "TITLE")
private String title;
...
}
How do I construct a URL such that the search bean's Event.getEventFeed().getId() field is populated?
I realize I could submit a GET request with a parameter like "eventFeedId=2" and populate everything manually, but since other pages are submitting requests that populate the command object, I'd like to continue to use the same logic.
It would be
/search_results.jsp?event.eventFeed.id=...&event.eventFeed.title=...
event is a default model attribute name as defined in #ModelAttribute, other binding rules are described in 5.4.1 Setting and getting basic and nested properties.
Note, however, that this approach can cause problems if you'll associate these bean with Hibernate session later. For example, if you want to attach new Event to the existing EventFeed by calling merge() it would also override the title property. Thus, in such a case it would be better to avoid overuse of data binding and pass primitives as parameters instead.

Resources