Bean validator: validate nested object while adding a prefix to its error messages - spring-boot

I'm having a problem where, when I have multiple nested beans of the same type, the returned message may end up being the same, which can confuse the user:
Minimal example (the real beans have lots of fields):
class A {
#NotBlank("Name is obligatory.")
String name;
#NotBlank("Address is obligatory.")
String name;
}
class B {
#Valid
A origin;
#Valid
A destination;
}
If I run B against the validator with blank names, it will always return "Name is obligatory.", no matter if it comes from the origin or from the destination. I know that the error message comes with the field names, but that information, by itself, is not very useful for the end user.
Is there some annotation that validates the nested beans similarly to what #Valid does, but adding a prefix, so that instead of saying "Name is obligatory.", it would say either "Original person: Name is obligatory." or "Destination person: Name is obligatory."?

I couldn't find any out-of-the box way to implement what was needed, so I had to implement a custom constraint:
PrefixConstraint.java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Constraint(validatedBy = PrefixConstraintValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface PrefixConstraint {
String message() default "Prefix missing";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
PrefixConstraintValidator.java
import java.util.Set;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
public class PrefixConstraintValidator implements ConstraintValidator<PrefixConstraint, Object> {
private PrefixConstraint constraint;
#Override
public void initialize(PrefixConstraint constraintAnnotation) {
this.constraint = constraintAnnotation;
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Object>> violations = validator.validate(value, this.constraint.groups());
context.disableDefaultConstraintViolation();
for (ConstraintViolation<Object> violation : violations) {
context
.buildConstraintViolationWithTemplate(this.constraint.message() + ": " + violation.getMessage())
.addPropertyNode(violation.getPropertyPath().toString())
.addConstraintViolation();
}
return violations.isEmpty();
}
}
Usage example:
class A {
#NotBlank("Name is obligatory.")
String name;
#NotBlank("Address is obligatory.")
String name;
}
class B {
#PrefixConstraint(message = "Original person")
A origin;
#PrefixConstraint(message = "Destination person")
A destination;
}
Now, if the names are left blank, the validator will return the message: "Original person: Name is obligatory" and "Destination person: Name is obligatory".

Related

How to get osgi configuration values to a servlet

This is my OSGI configuration file which is having three names. I want to read these values in a servlet and sort them Alphabetically and send that response to a ajax to display in a custom component AEM.
package com.demo.training.core.services.impl;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import com.demo.training.core.services.MyProjectServ;
#Component(service=MyProjectServ.class,immediate = true)
#Designate(ocd= MyProject.ServiceConfig.class)
public class MyProject implements MyProjectServ {
#ObjectClassDefinition(name="My-Project OSGI",
description="Demo OSGI configuration")
public #interface ServiceConfig {
#AttributeDefinition(
name="Name1",
description="Add First name",
type = AttributeType.STRING
)
public String Name1() default "Abhinay";
#AttributeDefinition(
name="Name2",
description="Add second name ",
type = AttributeType.STRING
)
public String Name2() default "Pavan";
#AttributeDefinition(
name="Name3",
description="Add third name ",
type = AttributeType.STRING )
public String Name3() default "Ram";
}
private String Name1;
private String Name2;
private String Name3;
#Activate
protected void activate(ServiceConfig myconfig) {
Name1=myconfig.Name1();
Name2=myconfig.Name2();
Name3=myconfig.Name3();
}
#Override
public String getNAME1() {
return Name1; }
#Override
public String getNAME2() {
return Name2; }
#Override
public String getNAME3() {
return Name3;
} }
'''This is my Servlet code , I have wrote multiple resp.getwriter() to see upto which line it is working. It is working upto response named a1(i.e below dictionary command). Could anyone please help to get values from osgi configuration to this servlet ?
package com.demo.training.core.servlets;
import java.io.IOException;
import java.util.Arrays;
import java.util.Dictionary;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.framework.Constants;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
#Component(service=Servlet.class,
property={
Constants.SERVICE_DESCRIPTION + "=Practice Servlet",
"sling.servlet.methods=" + HttpConstants.METHOD_GET,
"sling.servlet.methods=" + HttpConstants.METHOD_POST,
"sling.servlet.paths=/bin/myproject",
"sling.servlet.extensions=" + "txt"
})
public class MyProjectServlet extends SlingAllMethodsServlet {/**
*
*/
private static final long serialVersionUID = 1L;
#Reference
private ConfigurationAdmin MYPROJECT_CONFIG;
private static final String MY_PROJECT="com.demo.training.core.services.impl.MyProject";
#Override
protected void doGet(final SlingHttpServletRequest req,
final SlingHttpServletResponse resp) throws ServletException, IOException {
Configuration My_Servlet=MYPROJECT_CONFIG.getConfiguration(MY_PROJECT);
Dictionary<String,Object> property =My_Servlet.getProperties();
resp.getWriter().write("a1");
String first=property.get("Name1").toString();
String second=property.get("Name2").toString();
String third=property.get("Name3").toString();
resp.getWriter().write("a2");
resp.getWriter().write(first);
resp.getWriter().write("a3");
String[] myArray = new String[]{first,second,third};
Arrays.sort(myArray);
String js=myArray.toString();
resp.getWriter().write(js);
}
}
You try to use the #reference annotation for your service. If this object null you can use the ResourceResolverFactoy. This object does always exists, else you have your instance has a serious problem:
Map<String, Object> serviceParameter = new HashMap<>();
serviceParameter.put(ResourceResolverFactory.SUBSERVICE, Put the name name of your service here);
return resolverFactory.getServiceResourceResolver(serviceParameter);
In Servlet use annotation #reference to inject the ResourceResolverFactoy:
#Reference
private ResourceResolverFactory ...;
By the way, have an eye to java code convetions. Method names starts always with smal letters even in service configs.

Spring custom validator with dependencies on other fields

We are using spring custom validator for our request object used in our controller endpoint. We implemented it the same way as how its done in the link below:
https://www.baeldung.com/spring-mvc-custom-validator
The problem we are facing is, it can't work if the particular field has dependencies on other input fields as well. For example, we have the code below as the request object for our controller endpoint:
public class FundTransferRequest {
private String accountTo;
private String accountFrom;
private String amount;
#CustomValidator
private String reason;
private Metadata metadata;
}
public class Metadata {
private String channel; //e.g. mobile, web, etc.
}
Basically #CustomValidator is our custom validator class and the logic we want is, if the supplied channel from Metadata is "WEB". The field "reason" of the request won't be required. Else, it will be required.
Is there a way to do this? I've done additional research and can't see any that handles this type of scenario.
Obviously if you need access to multiple fields in your custom validator, you have to use a class-level annotation.
The same very article you mentioned has an example of that: https://www.baeldung.com/spring-mvc-custom-validator#custom-class-level-validation
In your case it might look something like this:
#Constraint(validatedBy = CustomValidator.class)
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomValidation {
String message() default "Reason required";
String checkedField() default "metadata.channel";
String checkedValue() default "WEB";
String requiredField() default "reason";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package com.example.demo;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.stereotype.Component;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/*
If the supplied channel from Metadata is "WEB". The field "reason" of the request won't be required.
Else, it will be required.
*/
#Component
public class CustomValidator implements ConstraintValidator<CustomValidation, Object> {
private String checkedField;
private String checkedValue;
private String requiredField;
#Override
public void initialize(CustomValidation constraintAnnotation) {
this.checkedField = constraintAnnotation.checkedField();
this.checkedValue = constraintAnnotation.checkedValue();
this.requiredField = constraintAnnotation.requiredField();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object checkedFieldValue = new BeanWrapperImpl(value)
.getPropertyValue(checkedField);
Object requiredFieldValue = new BeanWrapperImpl(value)
.getPropertyValue(requiredField);
return checkedFieldValue != null && checkedFieldValue.equals(checkedValue) || requiredFieldValue != null;
}
}
And the usage will be:
#CustomValidation
public class FundTransferRequest {
...
or with parameters specified:
#CustomValidation(checkedField = "metadata.channel",
checkedValue = "WEB",
requiredField = "reason",
message = "Reason required")
public class FundTransferRequest {
...

Validate image if existing or not null only in spring boot?

I'm trying to validate image in Spring boot with a custom messages and custom validator.
Here is my file path for the validator files
I just need to know how to check first if the image file is existing or not null and then validate it.
I need to mention that my image can be null value, in this case, I should not do the validation.
Here is the example for more clarification:
I first created the annotation as follows:
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {ImageFileValidator.class})
public #interface ValidImage {
String message() default "Invalid image file";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
I've created the validator as following:
import org.springframework.web.multipart.MultipartFile;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class ImageFileValidator implements ConstraintValidator<ValidImage, MultipartFile> {
#Override
public void initialize(ValidImage constraintAnnotation) {
}
#Override
public boolean isValid(MultipartFile multipartFile, ConstraintValidatorContext context) {
boolean result = true;
String contentType = multipartFile.getContentType();
if (!isSupportedContentType(contentType)) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(
"Only PNG or JPG images are allowed.")
.addConstraintViolation();
result = false;
}
return result;
}
private boolean isSupportedContentType(String contentType) {
return contentType.equals("image/png")
|| contentType.equals("image/jpg")
|| contentType.equals("image/jpeg");
}
}
Finally, applied the annotation as following:
public class CreateUserParameters {
#ValidImage
private MultipartFile image;
...
}
The code of the custom validator was here :
File upload in Spring Boot: Uploading, validation, and exception handling
by Wim Deblauwe in the last comment.
This is not really the answer for my question, but a temporary solution if someone has the same issue.
So the thing is, If the image is null, I will just tell the ImageFileValidator to send True to ValidImage and then the image will be validated.
Which means I don't have to send errors.
#Override
public boolean isValid(MultipartFile multipartFile, ConstraintValidatorContext context) {
boolean result = true;
String contentType = "";
try {
contentType = multipartFile.getContentType();
if(fileNameLength(multipartFile) == 0) {
return result;
}
} catch(NullPointerException e) {
e.printStackTrace();
}
if (!isSupportedContentType(contentType)) {
.....
The rest of the code can be found above. It might return NullException as well, so make sure to handle it.

Is there any way that we can use two custom error messages using spring-boot custom validation?

I'm using the below custom validation code to validate personName and it seems to be working fine, but the problem is when am passing an EMPTY string, it is giving same error message instead of the empty error message. Can someone please help me with this?
#Documented
#Constraint(validatedBy = {PersonNameValidator.class})
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Retention(RUNTIME)
#ReportAsSingleViolation
public #interface PersonName {
/**
* Default error message defined for the validator.
*
* #return message
*/
String message() default "invalid person name";
/**
* Method to define groups parameters for validation.
*
* #return groups
*/
Class[] groups() default {};
/**
* Method to load payload.
*
* #return payload
*/
Class<? extends Payload>[] payload() default {};
}
public class PersonNameValidator implements ConstraintValidator<PersonName, String> {
#Override
public boolean isValid(String name, ConstraintValidatorContext context) {
if (name.length() == 0) {
throw new IllegalArgumentException("must not be Empty");
} else if (!name.matches("(?=^(?!\\s*$).+)(^[^±!#£$%^&*_+§€#¢§¶•«\\\\/<>?:;|=]{1,256}$)")) {
throw new IllegalArgumentException("name should start with uppercase.");
}
return true;
}
}
#Data
public class NameDto {
#NotNull
#PersonName
private String family1Name;
#PersonName
private String family2Name;
#NotNull
#PersonName
private String givenName;
#PersonName
private String middleName;
}
Getting NullPointerException
#Name
#NotEmpty(message = "name cannot be empty")
String name;
should work
but if you want to join constraint you should use a custom ConstraintValidator add provide this validator via #Constraint(validatedBy = {YourCustomValidator.class}
see example below
full example
used dependency spring-boot-starter-validation (not needed if you use spring-boot-starter-web)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
application.properties
upper.name=dirk
application
package stackoverflow.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
#SpringBootApplication
public class SoCustomValidationApplication {
public static void main(String[] args) {
SpringApplication.run(SoCustomValidationApplication.class, args);
}
}
#Component
class ConfigurationLoader{
final MyCustomValidatedProperties config;
ConfigurationLoader(MyCustomValidatedProperties config){
this.config = config;
}
#EventListener()
void showName() {
System.err.println("name is: " + config.getName());
}
}
#org.springframework.context.annotation.Configuration
#Validated
#ConfigurationProperties(prefix = "upper")
class MyCustomValidatedProperties {
#Uppercase
#NotEmpty(message = "name cannot be empty")
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#Constraint(validatedBy = {ValidNameValidator.class})
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#ReportAsSingleViolation
#interface Uppercase {
String message() default "name should start with uppercase";
Class[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class ValidNameValidator implements ConstraintValidator<Uppercase, String> {
#Override
public boolean isValid(String name, ConstraintValidatorContext context) {
if (null == name || 0 == name.length() ) {
throw new IllegalArgumentException("name cannot be empty.");
} else if(!name.matches("^([A-Z][a-z]+)")) {
throw new IllegalArgumentException("name should start with uppercase.");
}
return true;
}
}
APPLICATION FAILED TO START
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'upper' to stackoverflow.demo.MyCustomValidatedProperties$$EnhancerBySpringCGLIB$$d0094cdb failed:
Property: upper.name
Value: dirk
Origin: class path resource [application.properties]:1:12
Reason: name should start with uppercase
and if you leave upper.name empty
upper.name=
APPLICATION FAILED TO START
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'upper' to stackoverflow.demo.MyCustomValidatedProperties$$EnhancerBySpringCGLIB$$29925f50 failed:
Property: upper.name
Value:
Origin: class path resource [application.properties]:1:12
Reason: name cannot be empty
Property: upper.name
Value:
Origin: class path resource [application.properties]:1:12
Reason: name should start with uppercase

Want to give custom annotation at RequestBody Pojo class

We can give #valid annotation but I want to give custom annotation at #RequestBody.
Use case: In my person Pojo class, I have two fields firstname and lastname. so I want to validate pojo class in that way that if user has given value for any field (like given for lastname) then it's good. but Both field should not be empty. User should give value for at least one field (it is either or condition)
My Pojo class:
class Person {
private String firstName;
private String lastName;
}
we can't give #NotNull for both fields. so I want to give custom annotation at the class level.
In that validator, I will check both fields and will send the proper error message to User.
You can try Custom ConstraintValidator, simple example #ValidatePerson:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#RestController
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#PostMapping
public Person doSomeThingPerson(#Validated #RequestBody Person person) {
return person;
}
#ValidatePerson
public static class Person {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
#Target({TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = {PersonValidator.class}) // you can use multiply validators
public #interface ValidatePerson {
String message() default "Invalid Person, firstName and lastName can't be null";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public static class PersonValidator implements ConstraintValidator<ValidatePerson, Person> {
#Override
public boolean isValid(Person person, ConstraintValidatorContext context) {
return person.getFirstName() != null || person.getLastName() != null;
}
}
}
If firstName and lastName both null then:
{
"timestamp": 1560456328285,
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"ValidatePerson.person",
"ValidatePerson"
],
"arguments": [
{
"codes": [
"person.",
""
],
"arguments": null,
"defaultMessage": "",
"code": ""
}
],
"defaultMessage": "Invalid Person, firstName and lastName can't be null",
"objectName": "person",
"code": "ValidatePerson"
}
],
"message": "Validation failed for object='person'. Error count: 1",
"path": "/"
}
Also you can customize exception with #ControllerAdvice

Resources