Making a Jersey bean validation annotation generic - java

I have a Jersey Rest API like this:
#POST
#Path("/doorder")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces("text/plain")
public String doOrder(#BeanParam final #Valid OrderBean order) {
// Some implementation here
}
All my inputs are store in this bean:
#AddressAtLeastOne
public final class OrderBean {
#FormDataParam("address")
private String address;
#FormDataParam("city")
private String city;
#FormDataParam("postcode")
private String postcode;
// Other member variables
// Getters and setters
}
I added an annotation to validate the address (#AddressAtLeastOne). The address is valid if at least one of the 3 fields has a value.
Here's the annotation definition:
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
#Retention(RUNTIME)
#Constraint(validatedBy = AddressAtLeastOneValidator.class)
#Documented
public #interface AddressAtLeastOne {
String message() default "Address requires at least one field";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And here's the validator:
public class AddressAtLeastOneValidator implements ConstraintValidator<AddressAllOrNone, OrderBean> {
#Override
public boolean isValid(OrderBean demoBean, ConstraintValidatorContext constraintValidatorContext) {
// Check for at least one value
if((demoBean.getAddress() != null && !demoBean.getAddress().equals("") ||
(demoBean.getCity() != null && !demoBean.getCity().equals("")) ||
(demoBean.getPostcode() != null && !demoBean.getPostcode().equals("")))) {
return true;
}
return false;
}
}
Everything is fine! But now I want to rename the annotation #AddressAtLeastOne to #AtLeastOne and make it generic, so that I can apply it to any class. I need a mechanism where I can specify which member variables are part of the group I want to validate with #AtLeastOne. How can I do that?

One approach of doing this is to use Reflection -
Create a custom annotation suppose #GroupNotNullField and apply this annotation to all fields in bean class in which at least one field should have value. By this way, you can skip some fields in which validation is not required.
In the validator class, get all the fields of the bean class using Reflection
Check all the fields which are annotated with #GroupNotNullField annotation
Get the value of all such fields and check that at least one has value.
Return true or false depending on validation check.

Related

Add custom validation message to enum

Is it possible to add a custom message to an enum if validation fails?
I have this enum class:
public enum EngineType{
FOO('F'),
BAR('B'),
QUX('Q');
private char id;
EngineType(char id) {
this.id = id;
}
public char getId() {
return this.id;
}
}
My model class contains private MyEnum myEnum;.
Currently if a value is passed in which isn't a valid enum, I get this BindingException:
{
"status": 400,
"validationErrors": {
"myEnum": "Failed to convert property value of type 'java.lang.String' to required type 'ie.aviva.services.motor.cartellservice.model.EngineType' for property 'myEnum'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#javax.validation.constraints.NotNull ie.aviva.services.motor.cartellservice.model.EngineType] for value 'corge';"
},
"title": "Bad Request"
}
My controller looks like this:
#RequestMapping(
method = RequestMethod.GET,
value = Endpoints.TRUE_MATCH,
produces = {"application/json"})
public ResponseEntity<ResponseWrapper<List<TrueMatch>>> getTrueMatch(
#Valid MyDetails MyDetails) {
LogContext.put(Constants.TAG, myDetails);
LOG.info(
"Controller called to get true match with my details: " + myDetails.toString());
...
}
MyDetails is like this:
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#SuperBuilder
public class MyDetails extends BasicDetails {
#NotBlank
#Pattern(
regexp = "^[a-zA-Z]*$",
message = "'${validatedValue}' contains unsupported characters")
private String name;
#NotNull private MyEnum myEnum;
...
}
Is it possible to change the message to some custom message of my own?
I'm already able to do this in annotations that I added to other variables by including the message parameter in the annotation. I tried creating an annotation to validate the pattern as seen here but it didn't work. I think because the annotation was added like this, which was too late. The exception had already been thrown:
#NotNull
#EnumNamePattern(regexp = "foo|bar|qux")
private MyEnum myEnum;
Sure. The simplistic solution would be to simply accept the value as a String, then wrap your parsing logic in a try-catch block, then return the result you want the user to see.
final MyEnum userValue;
try
{
userValue = MyEnum.valueOf(someStringInput);
}
catch (Exception e)
{
//respond to the user, either with an exception, or
//a proper response according to your application
}
Bean Validation & ControllerAdvice
You can make use of the Bean Validation and Exception-handling mechanisms provided by spring.
Note that OP is already using Bean validation in their project, which can be observed by the usage of annotations #NotBlank, #Pattern. So the following reference is exclusively for the Readers.
To include the Bean validation into your Spring Boot project, you can add Spring Boot Starter Validation dependency. In a nutshell, Bean validation is a specification (like for instance JPA) describing an IPA, which offers various annotations and interfaces for verifying that the data of domain objects is correct. And Hibernate Validator is an implementation of this specification which comes with Spring Boot Starter Validation.
To validate that the given String matches one of the enum-constants we would need a separate class, let's say MyDetailsDto. In case if you wonder why a new class? If the validation constant would be applied on the field of type enum it would validate only input that can be successfully parsed into an enum (otherwise an IllegalArgumentException would be thrown before applying the validation annotation), which can be useful only certain enum-constants can be assigned to a field. But that's not the case, we need to find out whether the given string is equal to one of the enum constants (by the way, in addition this approach creates an opportunity for sanitizing the input, for instance replacing some characters if needed).
#Getter
#Setter
public class MyDetailsDto {
private String name;
#EngineTypeConstraint
private String engineType;
public MyDetails toMyDetails() {
return new MyDetails(name, EngineType.valueOf(engineType));
}
}
So, to create a custom Validation constraint, we need two things: a custom annotation a Validator associated with it (for more information refer to the documentation - Configuring Custom Constraints):
Each bean validation constraint consists of two parts:
A #Constraint annotation that declares the constraint and its configurable properties.
An implementation of the jakarta.validation.ConstraintValidator interface that implements the constraint’s behavior.
We can find more information regarding the requirements for the custom annotation in the Bean validation documentation Constraint:
Each constraint annotation must host the following attributes:
String message() default [...]; which should default to an error
message key made of the fully-qualified class name of the constraint
followed by .message. For example
"{com.acme.constraints.NotSafe.message}"
Class<?>[] groups() default {}; for user to customize the targeted groups
Class<? extends Payload>[] payload() default {}; for extensibility purposes
So our custom annotation needs to have at least these three attributes, and we're interested primarily in the first one, providing a message which would be used in case if an exception occurs. Let's our annotation:
#Constraint(validatedBy = EngineTypeValidator.class)
#Documented
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface EngineTypeConstraint {
String message() default "Engine type does not exist";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator should implement generic interface ConstraintValidator, specifying the associated annotation and the validated type as its parameters.
That's how the implementation might look like:
public class EngineTypeValidator implements ConstraintValidator<EngineTypeConstraint, String> {
private static final Set<String> TYPE_NAMES =
EnumSet.allOf(EngineType.class).stream().map(Enum::name).collect(Collectors.toSet());
#Override
public boolean isValid(String engineType,
ConstraintValidatorContext constraintValidatorContext) {
if (!TYPE_NAMES.contains(engineType)) throw new EngineTypeNotValidException(engineType);
return true;
}
}
As you have probably noticed, validator throws a custom exception EngineTypeNotValidException which receives an invalid engine model as a parameter. We need this exception in order to build a response based on it.
public class EngineTypeNotValidException extends RuntimeException {
public EngineTypeNotValidException(String type) {
super(String.format("Engine type '%s' doesn't exist.", type));
}
}
To handle this exception and produces an error-response by using Controller Advice. For that, we need to define a class annotated with #ControllerAdvice and create a method marked with #ExceptionHandler to target the custom exception defined above.
#ControllerAdvice(assignableTypes = MyDetailsController.class)
public class ValidationExceptionHandler {
#ExceptionHandler(EngineTypeNotValidException.class)
public ResponseEntity<ErrorResponse> handleInvalidEngine(RuntimeException e) {
Throwable cause = e.getCause();
return ResponseEntity
.badRequest()
.body(new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
HttpStatus.BAD_REQUEST.getReasonPhrase(),
List.of(cause.getMessage())
));
}
}
Finally, to customize the response body, we can create a POJO with a couple of string field for representing the information of a failing response.
#AllArgsConstructor
#Getter
public class ErrorResponse {
private int status;
private String message;
private List<String> errors;
}
Demo
That's it now we can give it a try.
Consider the following dummy Controller:
#RestController
public class MyDetailsController {
#PostMapping("/newMyDetails")
public String newCar(#RequestBody #Valid MyDetailsDto dto) {
// some business logic
return "is valid";
}
}
And here's a couple screenshots from Postman with responses:
Valid request (hard-coded message from the Controller in the response):
Invalid request (response with a customized error message, prompting that enum-name provided in the request doesn't exist):

Spring Boot - Bean Validation, constraint annotation with generics

Spring Boot 2.3.1, with OpenJDK 14
Constraint Annotation:
#Documented
#Constraint(validatedBy = {MyConstraintAnnotationValidator.class})
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface MyConstraintAnnotation {
String message() default "";
Class[] groups() default {};
Class[] payload() default {};
Class clazz();
// or, not sure: Class<?> clazz();
}
Constraint Annotation Validator:
public class MyConstraintAnnotationValidator implements ConstraintValidator<MyConstraintAnnotation, String> {
#Override
public void initialize(MyConstraintAnnotation constraintAnnotation) {
// cac is: configurableApplicationContext
Object obj = cac.getBean(constraintAnnotation.clazz());
// problem: obj doesn't provide the methods to access
// on the left side of the variable shall be also the clazz type
// which is passed to the constraint annotation
}
#Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
return true;
}
}
Usage of constraint annotation:
public class MyPojo {
...
#MyConstraintAnnotation(clazz = MyProperties.class)
private String field1;
...
// Getter/ Setter
}
Properties class:
#Component
#PropertySource("classpath:myproperties.properties")
#ConfigurationProperties(prefix = "myprefix")
public class MyProperties {
// fields
// Getter / Setter
}
Via the constraint annotation I want to specify my properties class to be used in the validation process within the validator class.
But within the initialize(..) method I cannot access the Getter / Setter from my properties class (MyProperties.class), because configurableApplicationContext.getBean(...) returns Object, shall create a variable and cast it to the passed class (like MyProperties.class).
How to create a variable which has on the left side the same type which is passed to the constraint annotation?

How can a Spring Boot web app validate a form field that is required conditional on another field?

I have a Spring Boot web app in which fields of my form-backing bean are annotated with Bean Validation annotations (see Baeldung's tutorial or the docs at spring.io). For example, fields on Customer beans might be annotated like this:
#NotBlank
#Pattern(regexp="[ \\.A-Za-z-]*")
private String firstName;
#DateTimeFormat(pattern="M/d/yyyy")
#NotNull
#Past
private Date DOB;
What I want to know is: (How) Can I implement a complex validation that looks at multiple fields? I mean, using this framework. For example, let's say I have a field Country and a field ZipCode and I want the ZipCode to be #NotBlank if and only if the Country equals "US", optional otherwise.
In my Controller, the validation is very elegantly triggered with the use of the #Valid annotation, and errors are attached to a BindingResult object. Like so:
#PostMapping("customer/{id}")
public String updateCustomer( #PathVariable("id") Integer id,
#Valid #ModelAttribute("form") CustomerForm form,
BindingResult bindingResult ) {
if (bindingResult.hasErrors()) {
return "customerview";
}
customerService.updateCustomer(form);
return "redirect:/customer/"+id;
}
What I'm hoping to find is a way to write this conditional validator in a way that it, too, will be triggered by the #Valid annotation and will attach it's error message to the ZipCode field in the BindingResult. Ideally I shouldn't have to change the Controller code at all.
you can getting help of creating custom validator class, the following link helps you:
https://www.baeldung.com/spring-mvc-custom-validator#custom-validation
For this kind of stuff you need to use cross field validation. Please try :
Create annotation :
package foo.bar;
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {CustomerValidator.class})
public #interface CustomerValid {
String message() default "{foo.bar.CustomerValid.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Create custom validator :
public class CustomerValidator implements ConstraintValidator<CustomerValid, Customer> {
#Override
public void initialize(CustomerValid constraint) {
}
#Override
public boolean isValid(Customer customer, ConstraintValidatorContext context) {
return !("US".equals(customer.getCountry() && "".equals(customer.getZipCode())));
}
}
Annotate your class :
#CustomerValid
public class Customer {
// body
}
This class validation will be processed in addition to existent field validators.

What is the method signature means? String value() default "";

I came across this method signature in Spring Component interface.
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Indexed
public #interface Component
{
String value() default "";
}
What is the method signature String value() default ""; means?
How and When should we define such syntax for our coding purposes?
This is no method signature. it means that you can pass a String as parameter to the Component annotation, like this:
#Component(value = "value")
If you don't specify a value your self, the default value "" will be used.
If it had been like this:
String value(); // without the default
value would have been a mandatory parameter. Trying to use Component like this:
#Component()
would lead to an Exception, since you didn't provide a value.
EDIT: when to use.
If you don't know much about this syntax, or annotations in general, you shouldn't use them. About everything that can be done using annotations, especially custom made ones, can also be done without annotations.
Let's say you want to create an annotation to validate the value of a field.
I'll be using the example of Belgian postal codes. They all are 4 digits, and are between 1000 and 9999.
#Target( {ElementType.FIELD})
#Retention( RetentionPolicy.RUNTIME)
#Constraint( validatedBy = ValidatePostalCodeImpl.class)
public #interface ValidatePostalCode{
String message() default "You have entered an invalid postal code";
Class<?>[] groups() default {}; // needed for the validation
Class<? extends Payload>[] payload() default{}; // needed for the validation
int maxValue() default 9999; // so, by default, this will validate based
int minValue() default 1000; // on these values, but you will be able to
// override these
}
/* Validation implementation */
public class ValidatePostalCodeImpl implements ConstraintValidator<ValidatePostalCode, Integer> {
int upperValue;
int lowerValue;
#Override
public void initialize(ValidatePostalCode validate) {
this.upperValue = validate.maxValue(); // here you call them as if they indeed were regular methods
this.lowerValue = validate.minValue();
}
#Override
public boolean isValid(Integer integer, ConstraintValidatorContext context) {
return integer >= lowerValue && integer <= upperValue;
}
}
/* Usage */
#Entity
#Table(name = "addresses")
public class Addresses {
// home address -> In Belgium, so has to be between the default values:
#ValidatePostalCode
Integer belgianPostalCode;
// vacation home in another country, let's say the PC's there are between
// 12000 and 50000
#ValidatePostalCode(minValue = 12000, maxValue = 50000)
Integer foreignPostalCode;
}
Sure, this is a very limited example, but it should get you an idea.
The #interface keyword is used to define an annotation. This annotation has a property called value, which you could specify explicitly:
#Component(value = "myValue")
Or in the shorthand form:
#Component("myValue")
If you don't specify the value, it will default to "", as defined by the default keyword.

Validate UUID Restful service

I have a RESTful service which receives POST request with UUID values and writes them in DB. So the problem is to validate if UUID is valid or not. For this purpose I implemented custom annotation:
#Constraint(validatedBy = {})
#Target({ElementType.FIELD})
#Retention(RUNTIME)
#Pattern(regexp = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89ab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}")
public #interface validUuid {
String message() default "{invalid.uuid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
But for some reason it doesn't work, even if I pass valid UUID I constantly get an exception:
javax.validation.UnexpectedTypeException: HV000030: No validator
could be found for constraint 'javax.validation.constraints.Pattern'
validating type 'java.util.UUID'
Are there any options to validate UUID properly?
You cannot apply the #Pattern annotation to something (java.util.UUID) that is not a CharSequence. From the #Pattern annotation documentation (emphesizes mine):
Accepts CharSequence. null elements are considered valid.
Moreover, as far as I see you try to extend the behavior of the validation annotation handler by passing it to the new annotation definition.
If you wish to perform more complex validation, simply create your annotation without another validation annotations - their combining doesn't work like this. There must be something to recognize annotations and validate them.
#Target({ElementType.FIELD})
#Retention(RUNTIME)
#Constraint(validatedBy = UuidValidator.class)
public #interface ValidUuid {
String message() default "{invalid.uuid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Now, create a validator which implements ConstraintValidator<ValidUuid, UUID> and override the methods performing the validation itself.
public class UuidValidator implements ConstraintValidator<ValidUuid, UUID> {
private final String regex = "....." // the regex
#Override
public void initialize(ValidUuid validUuid) { }
#Override
public boolean isValid(UUID uuid, ConstraintValidatorContext cxt) {
return uuid.toString().matches(this.regex);
}
}
And apply the annotation:
#ValidUuid
private UUID uuId;
you can use UUID.fromString(...); and catch IllegalArgumentException

Categories

Resources