Custom form validation in Spring boot - spring-boot

I am working on a spring boot application and I have a password reset form. I am using a class like this to validate the inputs.
public class PasswordResetForm {
#NotEmpty
#Size(min=6, message="must be at least 6 characters")
private String password;
private String passwordConfirm;
//Getter and Setters
}
So, I now want to validate if the fields passwordConfirm and password are equals, I searched all over but could not find how to add a custom validation in this case. So, how do I add custom validation for other fields?
My controller's action looks like this
#RequestMapping(value = "/password-change/{id}-{tokenNumber}", method = RequestMethod.POST)
public String changePassword(#PathVariable String id, #PathVariable String tokenNumber, #Valid PasswordResetForm form, BindingResult formBinding, Model model) {
if (formBinding.hasErrors())
return "change-password";
//Other stuff
}

or if you wanna validate simply only this (passwordConfirm and password are equals) case.
you can use #AssertTrue.
#AssertTrue
public boolean isDifferentPass() {
return !password.equals(passwordConfirm);
}
if these two fileds are same , then your controller's BindingResult has error

For your needs, you could consider creating a custom #Constraint. You would first create the constraint annotation:
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy=MyConstraintValidator.class)
public #interface MyConstraint {
}
And then the constraint validator:
import javax.validation.ConstraintValidator;
public class MyConstraintValidator implements ConstraintValidator {
#Autowired;
private Foo aDependency;
...
}
You can find additional reference for this here:
Dependency Injection in JSR-303 Constraint Validator with Spring fails
And on the Spring Docs:
http://docs.spring.io/autorepo/docs/spring/3.2.x/spring-framework-reference/html/validation.html

You can use #Validated annotation for forcing validation of #RequestParam and #PathVariable. #Valid is for forcing validation of #RequestBody

Related

Spring boot rest requestbody and #valid not working when object is null/empty

I am trying to apply not null validation on an attribute of my request which is instructedAmount but it is not working. I have a Spring Boot (V2.3.0.RELEASE) application with the following endpoints:
#Validated
public class TestController {
#PostMapping(value = "/test/pay")
public ResponseEntity<IPSPaymentResponse> validatePayt(#Valid #RequestBody InstantPaymentRequest instantPaymentRequest) {
log.debug("start validatePayment method {}", instantPaymentRequest);
....
The InstantPaymentRequest is as follows:
#Data
#Validated
public class InstantPaymentRequest {
#Valid
private PaymentIdentificationRequest paymentIdentification;
#NotBlank(message = "transactionTypeCode.required")
private String transactionTypeCode;
#Valid
private InstructedAmount instructedAmount;
#Valid
private CustomerRequest debtor;
The instructed amount is as follows:
#Data
public class InstructedAmount {
#NotBlank(message = "currency.required")
private String currency;
#NotBlank(message = "value.required")
private String value;
}
Basically when the instructedAmount is provided in the payload but for example I miss currency attribute in payload, the validation is working fine, the non null error message is displayed correctly and my rest controller endpoint is not called.
However when instructedAmount is not provided in the payload, no mandatory error message is displayed and my rest endpoint is called, it this the correct way or I am missing something?
I thought since attribute of InstructedAmount cannot be null then InstructedAmount also cannot be null/empty.
How to add InstructedAmount not null validation in the above scenario with annotation?
Use #NotNull together with #Valid:
#NotNull
#Valid
private InstructedAmount instructedAmount;
From https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-object-graph-validation:
Note that null values are getting ignored during cascaded validation.

javax-validation annotation is not working for member variables that is declared as another ObjectType

I have added annotations in the parent class.
It is working fine.
But it is not working in the member variables that is declared as another Object type. It is validating:
orderId from base class
referenceNumber from MarchantApplicationRequest
#NotEmpty annotation at customerRequests field in MerchantApplicationRequest.
But it is not validating customerRoleType in CustomerRequest.
Also, I would like to add #NotBlank annotation in customerRequests. But it is not taking this, though it is taking #NotEmpty annotation.
Class MerchantApplicationRequest
#JsonIngnoreProperties(ignoreUnknown=false)
public class MerchantApplicationRequest extends IomBaseDTO {
#NotEmpty(message="customerRequests is mandatory")
private List<CustomerRequest> customerRequests;
#NotBlank(message="referenceNumber is mandatory")
private String referenceNumber ;
}
Class CustomerRequest
public class CustomerRequest {
#NotBlank(message="customerRoleType is mandatory")
private String customerRoleType ;
}
Controller class
Method where to apply validation:
#PostMapping("/orderDetail")
public void orderDetail(#Valid #RequestBody MerchantApplicationRequest request) {
try {
iOrderService.updateProductDetail(request);
} catch (Exception e) {
// ...
}
}
Here is my JSON payload:
{
"orderId" : 101,
"referenceNumber" : "123",
"customerRequests" : [ {
"customerRoleType" : null
}]
}
I am using in pom.xml of Spring Boot application:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
If you want to cascade the validation you have to add the #Valid annotation:
#Valid
#NotEmpty(message="customerRequests is mandatory")
private List<CustomerRequest> customerRequests;
Please read more about cascading in the Hibernate Validation documentation: Example 2.11: Cascaded validation
Using bean-validation (javax.validation), you can add validation to elements of collections.
Using Bean-Validation 1.0
#JsonIngnoreProperties(ignoreUnknown=false)
public class MerchantApplicationRequest extends IomBaseDTO {
#NotEmpty(message="customerRequests is mandatory")
#Valid
private List<CustomerRequest> customerRequests;
#NotBlank(message="referenceNumber is mandatory")
private String referenceNumber ;
}
See also:
JSR 303: How to Validate a Collection of annotated objects?
LogicBig Tutorial: Collection Validation
Alternative since Bean-Validation 2.0
In Java 8 generic types can also be validated by prepending the annotation before the type inside the diamond-operator, e.g. <#Valid CustomerRequest>. This is a more concise way of defining per-element validation. It has the same effect like the traditional way, validates every given element as defined in the class (CustomerRequest).
See also:
java/beans validation - collection/map does not contain nulls
Baeldung Tutorial: Validating Container Elements with Bean Validation 2.0

Spring Boot : How to do REST validation based on input group?

I have a form in which :
firstname and lastname are mandatory fields for registered user.
ssn for new user.
contract number for owner.
So, on clicking the submit button, REST API (connect API) is called with values
from either of the above groups.
My bean class has members :
FN
LN
SSN
contractNum
How do I validate using bean/hibernate validator and identify which group has been passed ?
From the Hibernate Documentation, you can read for detail
https://hibernate.org/validator/
Hibernate Validator allows to express and validate application
constraints. The default metadata source are annotations, with the
ability to override and extend through the use of XML. It is not tied
to a specific application tier or programming model and is available
for both server and client application programming. But a simple
example says more than 1000 words:
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class User {
#NotNull
private String firstName;
#NotNull
private String lastName;
#NotNull
private String ssn;
}
Bean Validation is best used for simple validation logic. If your validation requires more complexity, use Spring's Validator interface instead.
I don't know the context domain, so I'll just call your bean "Form" with all String fields for the example:
public class Form {
private String firstName;
private String lastName;
private String ssn;
private String contractNumber;
// getters and setters
}
Then create a validator for this class:
public class FormValidator implements Validator {
public boolean supports(Class clazz) {
return Form.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
Form form = (Form) target;
// validation logic
}
}
Then you can simply use it like this:
Form form = ...;
Validator validator = new FormValidator();
Errors errors = new Errors();
validator.validate(form, errors);
if (errors.hasErrors() {
// not valid
} else {
// is valid
}

Spring Data Rest PATCH requests do not fill transient properties not backed by a field

I want to implement a PATCH-Requese on an entity 'User' for changing the password with an additional transient property 'oldpassword' to compare it in the EventHandler.
The POST- and the PUT-request fill the property.
The PATCH-request doesn't: 'oldpassword' is null.
I'm using
spring-boot-starter-parent
spring-boot-starter-data-rest (2.1.6)
spring-boot-starter-web (2.1.6)
spring-boot-starter-data-jpa (2.1.6)
spring-data-jpa 2.1.9
spring-data-rest 3.1.9
spring-security 5.1.5 (presumably irrelevant)
I tried
the annotation #JsonProperty("oldpassword") (even though POST and PUT work).
the annotation #JsonDeserialize (JSON: #Transient field not seralizing)
to configure Jackson to disable the check for #Transient annotations (JPA Transient Annotation and JSON)
#JsonAutoDetect(fieldVisibility = Visibility.ANY) as class decorator
The simplified code is:
The entity 'User'
#Entity
public class User implements UserDetails, Serializable {
[...]
#NotNull
String password;
#Transient
String newpassword;
#Transient
String oldpassword;
public void setPassword(String password) {
this.newpassword = password;
}
public void setOldpassword(String oldpassword) {
this.oldpassword = oldpassword;
}
[...]
}
The Repository
#RepositoryRestResource(exported = true)
public interface UserRepository extends JpaRepository<User, Long> {
}
PATCH-Request (
HTTP Method = PATCH
Request URI = /api/users/2
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Authorization:"Basic aXJ0Z2VuZGFhczpFaW4gcGFzc3dvcmQ="]
Body = {
"username": "myusername",
"password": "mynewpassword",
"oldpassword": "theoldone"
}
The EventHandler
#Component
#RepositoryEventHandler(User.class)
public class UserEventHandler {
#HandleBeforeSave
public void printdata(User p) {
/* returns the new password*/
System.out.println("newpassword" + p.newpassword);
/* returns null (if it's a PATCH-request) */
System.out.println("oldpassword" + p.oldpassword);
/* returns the old persisted password */
System.out.println("password" + p.password);
}
}
The transient property 'newpassword' works, since I use the setter of the persisted property 'password'.
It seems you want to create a change-password feature. It won't work this way. Create a unique controller method for it.
It's not a standard REST request anyway.

Custom bean validation not working

In my Student class I have many fields which I am storing in the database and I also have one field to store photo( for that I am using MultiPartFile datatype) and I am validating this field using custom validation.
Below is code for validation
#Component
public class PhotoValidator implements Validator{
#Override
public boolean supports(Class<?> clazz) {
return Student.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Student student=(Student)target;
if(student.getStudentPhoto()!=null){
if(student.getStudentPhoto().getSize()==0){
errors.rejectValue("file", "missing.file");
}
}
if(!student.getStudentPhoto().getOriginalFilename().endsWith(".jpg")){
errors.rejectValue("file", "invalid.file");
}
}
}
In the controller I am implementing it like this
#InitBinder
protected void initBinderStudent(WebDataBinder binder) { binder.setValidator(photoValidator);
}
My Student Model is :-
#Entity
#Table(name = "STUDENT")
public class Student extends UrlEntity {
#Transient
MultipartFile studentPhoto;
#Column(name = "COURSE_TYPE", nullable = false)
#NotNull(message = "Course Type: Course Type can not be left blank")
private in.jmi.constants.CourseType courseType;
#OneToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "STUDENT_USER")
#Valid
private User user;
This custom validation of photo is not working and it also mess up the other annotation based validation that I am having here.
I have checked many posts in stackoverflow but couldn't find any relation to this particular problem.
Note:-If I remove the validation code from controller the code works just fine doing all the validations it is supposed to do.
You are mixing approaches in your example. You are not showing the imports in your code example, but the PhotoValidator class does not implement a Bean Validation constraints. It might be some Spring/JSF specific validator!?
To implement a Bean Validation constraint, you need to define a constraint annotation and at least one implementing ConstraintValidator. This is all described in Creating custom constraints. There are plenty of examples out there how to write a custom constraint.

Resources