Spring validate nested object with condition on parent - spring

What I want to achieve might be a bit weird but sadly I cannot change that easily. I have a DTO that has a nested resource. The nested resource gets validated and the validation it gets differs based on an attribute of its parent.
class rootDto {
#NotEmpty
private String type;
#Valid
private AddressDto address;
// Other attributes follow ...
}
Now AddressDto looks like the following:
class AddressDto {
#NotEmpty(/* ONLY IF rootDto.type IS 'xxx' */)
private String name;
#NotEmpty(/* ONLY IF rootDto.type IS 'yyy' */)
private String street;
// Other attributes follow ...
}
I read about class level contrains (example answer) but I don't think that this is what I want. I could always create a Validator for the rootDto and inside that have the conditional validations for the AddressDto but that would be ugly and will grow way too much since AddressDto is not the only nested resource I have that requires such validation.
So my main question is, can I somehow expose to the nested resource attributes from its parent in order to use them for the validation? Is there an alternative I have not found/thought?

If you are using Hibernate Validator as Bean Validation provider and are happy to use a non standard feature, you can use Hibernate Validator's GroupSequenceProvider SPI - http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html_single/#__literal_groupsequenceprovider_literal
The idea would be to work with validation groups which you programmatically activate by using a GroupSequenceProvider on your RootDto.

Related

Spring boot using placeholders in messages.properties

I'm working with Spring Boot 2.1.5, and I'm using javax annotations to validate my class properties. So for example, I have:
class MyClass {
#NotEmpty(message = "{validation.notEmpty}")
private String company;
}
Then I have a messages.properties file that contains:
validation.notEmpty={0} is missing
I was hoping to get the error message "company is missing", but instead I'm getting "{0} is missing". What is the proper way of passing my variable name as placeholder?
Thanks.
You cannot achieve such a thing because #NotEmpty resolves the placeholder and get that value as message, it doesn't pass it to the placeholder to be added as parameter.
Look at the default message :
javax.validation.constraints.NotEmpty.message = must not be empty
You can just pass a string as value.
Validation messages are not default not designed to hold the field name. That is retrievable from the ConstraintViolation objects that provide (among other things) paths for each validation error.
So to achieve your requirement, what you could do is creating a custom annotation/validator with a second attribute :
#NotEmpty(message = "{validation.notEmpty}", field="company")
private String company;
But doing it for every kind of constraints you use looks an overhead.
Maybe just accept the duplication that is simple to refactor :
#NotEmpty(message = "car is missing")
private String company;
Note that you loses the benefit from locales, which may be undesirable if you handle multiple languages.
In this case, a more robust approach would be to use ConstraintViolations that has the path value of the field.
If my view doesn't address your issue, I encourage to post the question in the Hibernate Validator issues tracker.

Spring data and hibernate - model validations - exception translation

I use spring-data and hibernate. Now I would like to apply some validation to my model. In most cases I would like to apply simple validation like null-checking etc. But in some cases I would like to apply more strict validation, such as email-validation. I found very useful feature in Hibernate validator - the #Email annotation. It works very well but here is the problem:
If i try to save a model with null value, then the following exception is thrown:
org.springframework.dao.DataIntegrityViolationException
But if I try to save a model with non-null but non-email value (let's say asdfgh), then the following exception is thrown:
javax.validation.ConstraintViolationException
I would love to see only one type of exception in both cases, because in both cases the model didn't pass the validation and I would like just to worry about only one exception type in my exception-handling code.
I tried to add PersistenceExceptionTranslationPostProcessor to my bean configuration, but it looks like it does not change anything.
Do you have an idea how to "unify" this exceptions?
Model:
#Entity
public class ValidationModel {
...
#Email
#Column(nullable = false)
private String email;
...
}
Repository:
public interface ValidationModelRepository extends JpaRepository<ValidationModel, Long> {
}
#Column(nullable = false) is not a validation check. It's a JPA constraint.
To validate that a value is not null, use #NotNull.

How to generate a value for a column in a JPA entity, while querying the database?

I have an entity that looks like this:
#Entity
#Table(uniqueConstraints={#UniqueConstraint(columnNames={"slug"})})
public class BlogPost {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String title;
#Column
private String slug;
}
I would like to generate the value of slug before persisting by doing the following:
Transforming the title from e.g. Blog Post Title to blog-post-title
Making sure that blog-post-title is unique in table BlogPost, and if it's not unique, I want to append some suffix to the title so it becomes e.g. blog-post-title-2
Since I need this on a lot of entities, my original idea was to create an EntityListener which would do this at #PrePersist. However, documentation generally states that I should not call EntityMan­ager or Query methods and should not access any other entity objects from lifecycle callbacks. I need to do that in order to make sure that my generated slug is indeed unique.
I tried to be cheeky, but it is indeed very hard to autowire a repository into an EntityListener with Spring anyway.
How should I best tackle this problem?
Thanks!
Both OndrejM and MirMasej are definitely right that generating a slug would not be something to be done in an Entity. I was hoping EntityListeners could be a little "smarter", but that's not an option.
What I ended up doing is using aspects to accomplish what I wanted. Instead of "hooking" into entities, I am rather hooking into save method of CrudRepository.
First, I created an annotation so I can recognize which field needs to be sluggified:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Slug {
/**
* The string slug is generated from
*/
String source() default "title";
/**
* Strategy for generating a slug
*/
Class strategy() default DefaultSlugGenerationStrategy.class;
}
Then, I created an aspect which is something like this:
#Aspect
#Component
public class SlugAspect {
... // Removed some code for bravity
#Before("execution(* org.springframework.data.repository.CrudRepository+.save(*))")
public void onRepoSave(JoinPoint joinPoint) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Object entity = joinPoint.getArgs()[0];
for (Field field: entity.getClass().getDeclaredFields()) {
Slug annotation = field.getAnnotation(Slug.class);
if (annotation != null) {
CrudRepository repository = (CrudRepository) joinPoint.getTarget();
Long count = 0L;
SlugGenerationStrategy generator = (SlugGenerationStrategy)annotation.strategy().newInstance();
String slug = generator.generateSlug(slugOrigin(entity));
if (id(entity) != null) {
Method method = repository.getClass().getMethod("countBySlugAndIdNot", String.class, Long.class);
count = (Long)method.invoke(repository, slug, id(entity));
} else {
Method method = repository.getClass().getMethod("countBySlug", String.class);
count = (Long)method.invoke(repository, slug);
}
// If count is zero, use the generated slug, or generate an incremented slug if count > 0 and then set it like so:
setSlug(entity, slug);
}
}
}
}
I put the code on github (though it's still just a proof of concept) if anyone is interested at: https://github.com/cabrilo/jpa-slug
It relies on having CrudRepository from Spring Data and having these two methods on a repo: countBySlug and countBySlugAndIdNot.
Thanks again for the answers.
The most straightforward solutions seems to make a check before setting the value of the title. It would mean however that the logic of calculating the slug would be outside of the entity and both would come from outside.
You have to think of an entity as a plain object without any connection to the database - this is the idea of ORM. However, you may pass a reference to EntityManager or DAO as an additional argument to a setter method, or somehow inject a reference to it. Then you may call a query directly from the setter method. The drawback of this solution is that you need to always provide EntityManager, either when you set title, or when you create/load the entity.
This is the best object oriented way of solving this problem.

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

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.

Spring: Same object, different validation

I have an object called User where I save all the data of the User. I have some annotations to perform validation and it works fine.
public class User{
#NotEmpty
#Email
#Size(max=100)
#Column(name="username", length=100, nullable=false, unique=true)
private String username;
#NotEmpty
#Size(min=5, max=40)
#Column(name="password", length=40, nullable=false)
private String password;
#Size(min=5, max=40)
#Transient
private String newPassword;
// other attributes ,getters and setters
}
I have two different forms (each one in a different page). In the first one I ask for username and password to create the user, so both of them are compulsory.
In the second form I show the information about the user: username, other data (which will be validated as well) andthe password and newPassword. If the newPassword is set and the password is the same as the user has I'll change the user's password, but if they are left empty it means I shouldn't change the password.
The problem is that I have two forms relating to the same object where there is a different validation for the field password. In the first one it must be not empty, but in the second one it can be empty.
In the controller I validate the object in this way:
public String getUserDetails(#Valid #ModelAttribute("User") User user, BindingResult result, Model model){
if(result.hasErrors()){
//There have been errors
}
...
}
but is password is empty there will be an error.
Is there any way to perform a validation only in some fields of the object?
Can I, at least, remove any validation error after the validation?
What is the best practice in this case?
Thanks
You can use JSR-303 constraint groups to achieve this.
public class User {
public interface GroupNewUser {};
#NotEmpty
private String username;
#NotEmpty(groups = {GroupNewUser.class});
private String password;
// ...
}
Then in the controller, instead of using #Valid, use Spring's #Validated annotation which allows specifying a constraint group to apply.
See this blog post and this one also for more information.
There is also this superb post for Spring usage.
This is the problem with declarative validation, it's not very easy to do this sort of thing.
The easiest solution is to remove the validation annotations from the password field, and validate that field manually in the controller. The BindingResult class has methods on it for you to explicitly mark fields as invalid.
Another alternative would be to create two subclasses of the form class, each with its own password field, one with validation annotations, and one without, and use the appropriate one in the appropriate place.
A few pointers. I'm not sure they will be useful, though:
Spring docs say that:
you may call binder.setValidator(Validator) within a #Controller's #InitBinder callback. This allows you to configure a Validator instance per #Controller class
javax.validation has two *Context interfaces. Get into more details with them to see whether different validation can be achieved in different contexts.
instead of using hasErrors() function, use hasFieldErrors(fieldname) and only validate particular fields required by the form.

Resources