How to get parameter of my Annotation as a validator? - java

I created my own Annotation to validate my REST parameter like this:
#PostMapping("/users")
#ResponseStatus(HttpStatus.CREATED)
public User createUser(#UserConstraint("CREATE_MODE") #RequestBody User user)
{ //code }
I got everything working where my ConstraintValidator is called to validate the User input, but I can't figure out how to get the parameter of my own annotation. I want to read the value CREATE_MODE.
#Constraint(validatedBy = UserValidator.class)
#Target(ElementType.PARAMETER )
#Retention(RetentionPolicy.RUNTIME)
public #interface UserConstraint {
String message() default "";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
public String value();
}
How to access??
public class UserValidator implements ConstraintValidator<UserConstraint, User> {
#Override
public boolean isValid(User user,
ConstraintValidatorContext context) {
???
}

#Override
public void initialize(UserConstraint annotation) {
// initialization, probably not needed
mode = annotation.value();
}

Related

#RequestBody as a String is not being validated

I do have simple controller which accept String as RequestBody
#RequestMapping(value = "/test", method = RequestMethod.POST)
public ResponseEntity doSmth(#RequestBody #ValidTest String val) {
//do something
return ResponseEntity
.status(HttpStatus.OK)
.body("saved");
}
But for some reason val param not being validated with TestConstraintValidator.class
#Documented
#Constraint(validatedBy = TestConstraintValidator.class)
#Target({PARAMETER})
#Retention(RUNTIME)
public #interface ValidTest{
String message() default "Invalid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Is there even option to validate this? Or do I need to wrap this String withing custom class. And validate it there?
public class TestConstraintValidator implements ConstraintValidator<ValidTest, String> {
#Override
public void initialize(ValidTest constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(String val, ConstraintValidatorContext constraintValidatorContext) {
return false;
}
}
Make sure the controller class is marked as #Validated.

Custom validation for #PathVariable

There is a controller accepting code as a path variable
#RestController
#RequestMapping(value = "/api/currency")
#Validated
public class CurrencyController {
#GetMapping("/gif/{code}")
public ResponseEntity<Map> getChangeGif(#PathVariable #Code String code){
// some implementation
return null;
}
}
I want to use my own annotation to validate code as I want
#Target( { FIELD, PARAMETER })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = CodeValidator.class)
public #interface Code {
public String message() default "error message";
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default {};
}
And here is the validator
public class CodeValidator implements ConstraintValidator<Code, String> {
#Override
public void initialize(Code constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(String code, ConstraintValidatorContext context) {
// validator implementation
return false;
}
}
For some reason when requests come, validation just skipps, and controller continue working without it

Validating a lambda request in Micronaut using ConstraintValidator

I have a handler like this and a custom annotation #ValidRequest:
#Introspected
public class MessageHandler extends MicronautRequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
#Override
public APIGatewayProxyResponseEvent execute(#ValidRequest APIGatewayProxyRequestEvent event) {
return new APIGatewayProxyResponseEvent()
.withStatusCode(200)
.withHeaders(Collections.singletonMap("Content-Type", "application/json"))
.withBody("OK");
}
}
The annotation itself looks like this:
#Retention(RUNTIME)
#Constraint(validatedBy = {ValidRequestValidator.class})
public #interface ValidRequest {
String message() default "Request is not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And the validator is like this:
#Introspected
public class ValidRequestValidator implements ConstraintValidator<ValidRequest, APIGatewayProxyRequestEvent> {
#Override
public boolean isValid(
#Nullable APIGatewayProxyRequestEvent event,
#NonNull AnnotationValue<ValidRequest> annotationMetadata,
#NonNull ConstraintValidatorContext context
) {
if (event == null || event.getBody() == null || event.getBody().isEmpty()) {
throw new RuntimeException("Incorrect request event");
}
return true;
}
}
The problem is that validation is completely ignored. I can send any events with or without body and everything works without exception. I did everything according to the Micronout documentation, what could be wrong?
https://docs.micronaut.io/latest/guide/index.html#beanValidation
Please remove the #Introspect from your validator and try to follow the same as I have done below.
#POST
#Consumes({MediaType.APPLICATION_JSON})
#Produces(MediaType.APPLICATION_JSON)
#Path("/somePath")
public Response workingWithSubscription( #Valid UpdateSubscription updateSubscription) {
#ValidUpdateSubscription
public class UpdateSubscription implements UpdateSubscriptionRequest {
}
#Target({TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = UpdateSubscriptionValidator.class)
#Documented
public #interface ValidUpdateSubscription {
int ERROR_CODE = 1111;
String message() default "Null value";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int errorCode() default ERROR_CODE;
Response.Status status() default Response.Status.BAD_REQUEST;
}
#Named
#ApplicationScoped
public class UpdateSubscriptionValidator implements ConstraintValidator<ValidUpdateSubscription, UpdateSubscription> {
#Override
public boolean isValid(UpdateSubscription value, ConstraintValidatorContext context) {
return true/OR/false;
}
}

Custom parameter validator in spring boot

I have created a custom validator to validate the String passed to the function in converter. However, the custom validator is not being called. Am I missing something?
OperationParameter.java
#Documented
#Constraint(validatedBy = OperationParameterValidation.class)
#Target( { ElementType.PARAMETER
})
#Retention(RetentionPolicy.RUNTIME)
public #interface OperationParameter {
String message() default "Operation Parameter Invalid";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
OperationParameterValidation.java
public class OperationParameterValidation implements ConstraintValidator<OperationParameter, String> {
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
System.out.println("Validator called");
// validation process
// return true / false;
}
}
converter.java
#Component
public class StringToOperation implements Converter<String, Operation> {
#Override
public Operation convert(#Valid #OperationParameter String source) {
// Even I pass wrong String this function is executed successfully, and no print from validator
}
}
Service.java
public class Service {
#Autowired
ConversionService conversionService;
public void action() {
String action = "";
Operation addInsertOperation = conversionService.convert(action, Operation.class);
}
}
Set #SupportedValidationTarget(ValidationTarget.PARAMETERS) on validator class

Hibernate Validator - Add a Dynamic ConstraintValidator

After learning about Hibernate Custom Validators, it has given me an interest in one topic, could I possibly create one base annotation wherein I could set which Validator to use?
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = validator().class)
public #interface CustomAnnotation {
public String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends ConstraintValidator<? extends CustomAnnotation, Serializable>> validator();
}
So that I could use #CustomAnnotation in this manner
#CustomAnnotation(validator = CustomConstraintValidator.class, message = "validationMessage")
private Object fieldName;
I would not recommend it but you can do it roughly this way:
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = GenericValidatorBootstrapperValidator.class)
public #interface CustomAnnotation {
public String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends ConstraintValidator<? extends CustomAnnotation, Serializable>> validator();
}
public class GenericValidatorBootstrapperValidator implements ConstraintValidator<CustomAnnotation, Object> {
private final ConstraintValidator validator;
#Override
public void initialize(CustomAnnotation constraintAnnotation) {
Class<? extends ConstraintValidator> validatorClass = constraintAnnotation.validator();
validator = validatorClass.newInstance();
validator.initialize( ... ); //TODO with what?
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
return validator.isValid(value, context);
}
}
But again, prefer specific annotations, they are more expressive.
Edit
After your comment, I think what you want is to be able to set different validators based on the return type of the property
#CustomAnnotation
List<String> foo;
#CustomAnnotation
Table bar;
If that's the case, add several validators implementations in the #Constraint annotation.
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {ListValidatorImpl.class, TableValidatorImpl.class, ...})
public #interface CustomAnnotation {
public String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class ListValidatorImpl implements ConstraintValidator<CustomAnnotation, List> {
#Override
public boolean isValid(List value, ConstraintValidatorContext context) {
}
}
public class TableValidatorImpl implements ConstraintValidator<CustomAnnotation, Table> {
#Override
public boolean isValid(Table value, ConstraintValidatorContext context) {
}
}
You can even link a contraint annotation with an implementation via the META/validation.xml file
<constraint-mappings
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping validation-mapping-1.1.xsd"
xmlns="http://jboss.org/xml/ns/javax/validation/mapping" version="1.1">
<constraint-definition annotation="org.mycompany.CustomAnnotation">
<validated-by include-existing-validators="true">
<value>org.mycompany.EnumCustomValidatorImpl</value>
</validated-by>
</constraint-definition>
</constraint-mappings>
If you need something more flexible, I think my initial proposal would work. In the GenericValidatorBootstrapperValidator isValid method, you could call the right validator instance based on the object type of the value parameter (via instanceof for example).
Hibernate Validator also offers now a annotation #ScriptAssert which makes the implementation of custom validations easier and helps to avoid plenty lines of code.
Example of use:
#ScriptAssert(lang = "javascript",
script = "_this.capital.equals(_this.capital.toUpperCase)",
message = "capital has not Capital letters")
public class BigLetters {
private String capital;
public String getCapital() {
return capital;
}
public void setCapital(String capital) {
this.capital = capital;
}
}
I don't think you can implement a dynamic validator resolver on top of Hibernate Validator support. It's much better to have a dedicated set of annotation-validator pairs so when you annotate a field with a specific Validation annotation, it's clear what Validator will be used.

Categories

Resources