Method level custom constraint not fired on Spring controller REST method - java

I'm creating a custom method level constraint for a Spring REST method but the validator is not being triggered at all. The isValid method is never called.
//
// Controller
//
#PostMapping("/{id}")
#DTOParametersMatch
public ResponseEntity<DTO> createDTO(
#PathVariable("id") #SuppressWarnings("unused") UUID id,
#Validated(CreateValidation.class) #RequestBody DTO dto
) {
System.out.println("***** createDTO called");
DTO created = dtosService.createDTO(dto);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
//
// DTOParametersMatch
//
#Constraint(validatedBy = DTOParametersMatchValidator.class)
#Target({ METHOD, CONSTRUCTOR })
#Retention(RUNTIME)
#Documented
public #interface DTOParametersMatch {
String message() default "DTO ID in path must match DTO ID in body";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
//
// DTOParametersMatchValidator
//
#SupportedValidationTarget(PARAMETERS)
public class DTOParametersMatchValidator implements ConstraintValidator<DTOParametersMatch, Object[]> {
private static final String ILLEGAL_ARGS_BASE =
"Illegal usage of DTOParametersMatch; "
+ "requires two parameters where first is a UUID and second is a DTO.";
#Override
public boolean isValid(Object[] values, ConstraintValidatorContext context) {
System.out.println("***** DTOParametersMatchValidator isValid called");
var pathId = (UUID)v0;
var body = (DTO)v1;
return pathId.equals(body.getId());
}
}
I'm not sure if I missed something or set it up wrong..

One of my colleagues pointed out I was missing the #Validated annotation on the controller class. Once I added that, everything worked.

Related

Can I use a custom validator for a RequestBody that is a string?

I have created a custom validator that should validate if the body of a request (a simple string) is in Json format. I see that the custom validator is never called. Here are some parts of my code:
#RequestMapping(value = "/endpoint", method = { RequestMethod.POST })
#ResponseBody
public ResponseEntity<String> authorize(#RequestBody #MyValidator String token) {
// logic
}
This is the annotation:
#Target({ ElementType.PARAMETER })
#Retention(RUNTIME)
#Constraint(validatedBy = { JsonSyntaxValidator.class })
#Documented
public #interface MyValidator {
String message() default "{Token is not in Json syntax}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
This is the validator:
public class JsonSyntaxValidator implements ConstraintValidator<MyValidator, String> {
#Override
public void initialize(JsonFormat constraintAnnotation) {
}
/**
* It returns true if the Google Pay or Apple Pay token is in Json format.
*/
#Override
public boolean isValid(String token, ConstraintValidatorContext constraintValidatorContext) {
boolean isValid = true;
try {
JsonParser.parseString(token);
} catch (JsonSyntaxException e) {
isValid = false;
}
return isValid;
}
}
I have tried invoking the endpoint with postman passing it a string not formatted as json and in debug I see that the check is skipped past.
I don't want to have a POJO with fields, I just want the request body as a string.
I haven't found much online, only a post stating that it might not be possible.
Any help would be really appreciated :)
Your controller should be using the #Valid annotation instead of #MyValidator. I updated your controller below to what should work.
public ResponseEntity<String> authorize(#Valid #RequestBody String token) {
// logic
}

Custom annotation isn't validating method parameter

I created a custom annotation
#Documented
#Constraint(validatedBy = CheckGranularityValidator.class)
#Target( { ElementType.PARAMETER} )
#Retention(RetentionPolicy.RUNTIME)
public #interface CheckGranularity {
String message() default "Duration has to be a multiple of granularity";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
With a validator like so
public class CheckGranularityValidator implements ConstraintValidator<CheckGranularity, AssetCostsRequest> {
#Override
public void initialize(final CheckGranularity constraintAnnotation) {
}
#Override
public boolean isValid(final AssetCostsRequest value, final ConstraintValidatorContext context) {
return value.getRange().getDuration() % value.getGranularity() == 0;
}
}
I tried using it in my RestController
#RestController
public class CalcApiController extends CalcApi {
#Override
public ResponseEntity<String> calcProfitability(#Valid #CheckGranularity #RequestBody final AssetCostsRequest assetCostsRequest) {
return ResponseEntity.ok("Works");
}
I tried using this annotation by writing a test:
#Test
public void calcTest() {
final AssetCostsRequest request = new AssetCostsRequest()
.setRange(new TimeRange(100L, 200L))
.setGranularity(26L);
given()
.contentType(ContentType.JSON)
.body(request)
.when()
.post("/calc")
.then()
.statusCode(HttpStatus.SC_BAD_REQUEST);
}
Relevant part of AssetCostsRequest:
public class AssetCostsRequest {
#JsonProperty
#NotNull
private TimeRange range;
#JsonProperty
#NotNull
private Long granularity = 30L;
...getters & setters
}
Test method returns with 200. When I try to set a breakpoint in isValid method, it isn't hit when I run the test. I tried changing order of annotations, getting rid of #Valid, changing #Target in CheckGranularity class, nothing helped. I'm using RestAssured for testing.
How do I make it, so my annotation is properly validating a parameter?
Change CheckGranularity's target to ElementType.TYPE and add #CheckGranularity directly on AssetCostsRequest. Also remove #CheckGranularity from endpoint definition.
How it works. By adding #Valid on endpoint's parameter you tell spring to validate it. Adding validation like #CheckGranularity won't work on the same level as Valid. It has to be added somewhere inside parameters class.

How to enable javax annotations when value not null and disabled when value is null?

I need to disable validation if the value of email is null and check on it if the value is not null.
#Email(message = "{invalidMail}")
private String email;
I found the answer and it's that almost all java validation annotations accepts null so if my value is null it's going to accept it otherwise it will check.
You can't achieve this result with the predefined set of validation annotations only.
You have to create a custom validation annotation which performs the validation based on the specifications. You can get inspired on the Baeldung's article Spring MVC Custom Validation.
Here is the annotation.
#Documented
#Constraint(validatedBy = MyEmailValidator.class) // Class which performsthe validation
#Target({ ElementType.FIELD }) // Applicable to a field
#Retention(RetentionPolicy.RUNTIME)
public #interface MyEmail {
String message() default "The email is invalid"; // The default message
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And a class which actually performs the validation itself against the email Regex if the input is not null. Returns true otherwise as null is accepted. Note this class has to implement ConstraintValidator<A extends Annotation, T>.
public class MyEmailValidatorimplements ConstraintValidator<MyEmail, String> {
// Email Regex
private final String emailPattern= "[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}";
#Override
public void initialize(MyEmail myEmail) { }
#Override
public boolean isValid(String input, ConstraintValidatorContext cxt) {
if (input == null) {
return true;
} else return Pattern.matches(emailPattern, input);
}
}

Problems extending a Hibernate Validator constraint

I am trying to extend the behavior of the #NotBlank constraint to apply to URIs by making a custom constraint called #NotBlankUri.
Here's my constraint annotation:
#Target({ METHOD, FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = NotBlankUriValidator.class)
public #interface NotBlankUri {
String message() default "{project.model.NotBlankUri.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and here is the ConstraintValidator:
public class NotBlankUriValidator implements ConstraintValidator<NotBlankUri, URI> {
public void initialize(NotBlankUri annotation) {
}
public boolean isValid(URI uri, ConstraintValidatorContext context) {
NotBlankValidator nbv = new NotBlankValidator();
return nbv.isValid(uri.toString(), context);
}
}
Problem is that the isValid() method on the ConstraintValidator is getting null values for the URI argument. I thought this wasn't supposed to happen given the fact that #NotBlank itself is annotated #NotNull. That not being the case, I tried adding #NotNull as a meta-annotation to my #NotBlankUri, but that didn't have the desired effect either. How can I make my annotation constraint behave like #NotBlank, which seems to be stacking on top of the behavior of #NotNull?
As per the documentation, you can't use the #NotBlank annotation on a datatype that is not a String.
public #interface NotBlank
Validate that the annotated string is not null or empty. The difference to NotEmpty is that trailing whitespaces are getting ignored.
So if you declared your validator to validate a String, everything would be fine and you could write your annotation like this:
#Target({ METHOD, FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = NotBlankUriValidator.class)
#NotBlank
public #interface NotBlankUri {
String message() default "{project.model.NotBlankUri.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
If you are deadset on using the URI class 1 you need to perform custom validation logic yourself like this:
Annotation:
#NotNull(message="URI must not be null")
#Target({ METHOD, FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = NotBlankUriValidator.class)
public #interface NotBlankUri {
String message() default "URI must not be blank";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator:
public class NotBlankUriValidator implements ConstraintValidator<NotBlankUri, URI> {
public void initialize(NotBlankUri annotation) {
}
public boolean isValid(URI uri, ConstraintValidatorContext context) {
boolean isValid = true;
System.out.println("URI: " + uri);
//Leave null checks to your #NotNull constraint.
//This is only here to prevent a NullPointerException on the next check.
if(uri == null){
return true;
}
if(uri.toString().isEmpty()){
isValid = false;
}
return isValid;
}
}
I ran the above with a test harness:
public class UriContainer {
public UriContainer(URI uri){
this.uri = uri;
}
#NotBlankUri
private URI uri;
public URI getUri() {
return uri;
}
}
public static void main(String[] args) throws URISyntaxException{
UriContainer filledContainer = new UriContainer(new URI("Stuff"));
UriContainer emptyContainer = new UriContainer(new URI(""));
UriContainer nullContainer = new UriContainer(null);
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<UriContainer>> filledViolations = validator
.validate(filledContainer);
Set<ConstraintViolation<UriContainer>> emptyViolations = validator
.validate(emptyContainer);
Set<ConstraintViolation<UriContainer>> nullViolations = validator
.validate(nullContainer);
System.out.println("Filled: ");
filledViolations.stream().forEach(System.out::println);
System.out.println("Empty: ");
emptyViolations.stream().forEach(System.out::println);
System.out.println("Null: ");
nullViolations.stream().forEach(System.out::println);
}
which output the following violations:
URI: Stuff
URI:
URI: null
Filled:
Empty:
ConstraintViolationImpl{interpolatedMessage='URI must not be blank', propertyPath=uri, rootBeanClass=class sandbox.UriContainer, messageTemplate='URI must not be blank'}
Null:
ConstraintViolationImpl{interpolatedMessage='URI must not be null', propertyPath=uri, rootBeanClass=class sandbox.UriContainer, messageTemplate='URI must not be null'}
As you can see, this allows you to output different error messages based on if the URI is blank or null. Just make sure if you are using a javax.validation annotation you check which datatype you operate on.
1: which by the way, performs validation when you construct the object, and will throw a URISyntaxException if the String passed to the constructor violates RFC 2396

Spring validation annotation - How can I verify that a 2 character entry into a string is an actual US state?

I am trying to use spring to check user online input to ensure that the two characters they enter is an actual US state, is there any way of doing this, hopefully using a preset pattern? like, #State or something (if that was a legit annotation). Also, is there a good annotation commonly used for a String street, and String city field? That is other than #NotNull and #NotEmpty
Any help would be greatly appreciated!!
Unfortunately there is no out of the box however you can create your own #State annotation , all you need is to define your annotation and class implementing ConstraintValidator(which handles the validation logic) E.g.
#Constraint(validatedBy = StateConstraintValidator.class)
#Target( { ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface State {
String message() default "{State}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class StateConstraintValidator implements ConstraintValidator<String, String> {
private static final Set<String> CODE_MAP = new HashSet<>(){
{add("AR");}
{add("AK");} //add more codes ...
};
#Override
public void initialize(String state) { }
#Override
public boolean isValid(String value, ConstraintValidatorContext cxt) {
if(value == null) {
return false;
}
return CODE_MAP.contains(value);
}
}
In the similar manner you can create other annotations.

Categories

Resources