I have a rest service with my request body bean annotated with javax.validation like #NotBlank #NotNull #Pattern etc., and in one specific field I receive a file encoded as a string base64,
so, is there an annotation, or how could I write a custom validation annotation, so it would check if the string is really a base64 string?
I just need a validation like this in annotation form:
try {
Base64.getDecoder().decode(someString);
return true;
} catch(IllegalArgumentException iae) {
return false;
}
thnx in advance
Yes, you could write your own annotations and validators for them.
Your annotation would look like this:
#Documented
#Constraint(validatedBy = Base64Validator.class)
#Target( { ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface IsBase64 {
String message() default "The string is not base64 string";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Constraint validator javax.validation implementation (I'm using here your code for the actual validation):
public class Base64Validator implements ConstraintValidator<IsBase64, String> {
#Override
public boolean isValid(String value, ConstraintValidatorContext cxt) {
try {
Base64.getDecoder().decode(value);
return true;
} catch(IllegalArgumentException iae) {
return false;
}
}
}
Example data class with the annotated field:
#Data
public class MyPayload {
#IsBase64
private String value;
}
And controller method example with #Valid annotation which is required:
#PostMapping
public String test(#Valid #RequestBody MyPayload myPayload) {
return "ok";
}
Related
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
}
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.
I am trying to validate my rest request according to some fields existance. For example, if transactionDate field is null or didnt exist in my request object, I want to throw an error to client.
I couldn't do it despite the source of this guide and still my requests can pass in controller.
How can I validate two or more fields in combination?
DTO
#FraudRestRequestValidator
public class FraudActionsRestRequest {
private BigDecimal amount;
private String receiverTransactionDate;
private String receiverNameSurname;
private BigDecimal exchangeRate;
private String transactionReferenceNumber;
#NotNull
private String transactionDate;
#NotNull
private String transactionTime;
private String transactionTimeMilliseconds;
private BigDecimal tlAmount;
private String channel;
}
ANNOTATION
#Constraint(validatedBy = FraudActionsRestValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface FraudRestRequestValidator {
String message() default "Invalid Limit of Code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
VALIDATOR
public class FraudActionsRestValidator implements ConstraintValidator<FraudRestRequestValidator, FraudActionsRestRequest> {
#Override
public void initialize(FraudRestRequestValidator constraintAnnotation) {
}
#Override
public boolean isValid(FraudActionsRestRequest fraudActionsRestRequest, ConstraintValidatorContext constraintValidatorContext) {
//I will implement my logic in future
return false;
}
}
REST CONTROLLER
#PostMapping("/getFraudActions")
public ResponseEntity<?> getFraudActions(#Valid #RequestBody FraudActionsRestRequest fraudActionsRestRequest, Errors errors) throws Exception
Thanks.
In your custom validator just implement logic you want to have. You did everything correct except some minor thing:
#Constraint(validatedBy = FraudActionsRestValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidFraudRestRequest {
String message() default "Invalid Limit of Code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class FraudActionsRestValidator implements ConstraintValidator<ValidFraudRestRequest, FraudActionsRestRequest> {
#Override
public void initialize(ValidFraudRestRequest constraintAnnotation) {
}
#Override
public boolean isValid(FraudActionsRestRequest fraudActionsRestRequest, ConstraintValidatorContext constraintValidatorContext) {
return fraudActionsRestRequest.getTransactionDate() != null && fraudActionsRestRequest.getTransactionTime() != null && additional check you need;
}
}
Looks all okaish.
You might be missing the #Validated annotation on the rest controller class,
See https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-validation.html for more info
I want to Implement a validation in a jersey such that if I send a duplicate value of UserName or Email which already exists in DataBase then it should throw an Error saying UserName/Email already exists.
How can I acheive this?
I gone through this jersey documentation
https://jersey.java.net/documentation/latest/bean-validation.html
https://github.com/jersey/jersey/tree/2.6/examples/bean-validation-webapp/src
But I couldn't understood what exactly I have to follow to make my custom Jersey validations.
Suppose I send a Json in Body while Creating a User like:
{
"name":"Krdd",
"userName":"khnfknf",
"password":"sfastet",
"email":"xyz#gmail.com",
"createdBy":"xyz",
"modifiedBy":"xyz",
"createdAt":"",
"modifiedAt":"",
}
Thanks in Advance for your helping hands.
Assuming you have a request instance of class:
public class UserRequest {
// --> NOTICE THE ANNOTATION HERE <--
#UniqueEmail(message = "email already registered")
private final String email;
public UserRequest(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
}
You have to add a new annotation (and link it to your validator class using #Constraint):
#Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = { UniqueEmailValidator.class })
#Documented
public #interface UniqueEmail {
String message();
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
then you also have to implement the validation itself:
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, UserRequest> {
#Override
public void initialize(UniqueEmail constraintAnnotation) {
}
#Override
public boolean isValid(UserRequest value, ConstraintValidatorContext context) {
// call to the DB and verify that value.getEmail() is unique
return false;
}
}
and you're done. Remember that Jersey is using HK2 internally so binding some sort of a DAO to your Validator instance can be tricky if you use Spring or other DI.
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