I’m checking if two fields contain and invalid characters, if so, output an error message.
This works fine when its just one field that is invalid but when both contain invalid characters, I need a single error message with details of both fields.
Example:
Single invalid field = Fields contain invalid characters: account number '6637=958'
Both field invalid = Fields contain invalid characters: account number '6637=958', sort code '01%657'
I can’t change the format of the error messages. Right now, I just use a stream to output the first error message. I would like to keep the solution simple but can only think of brute force ways to combine the messages.
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
#JsonIgnoreProperties(ignoreUnknown = true)
public class AccountDetails {
#Pattern(regexp = "^[-0-9]*$", message="Fields contain invalid characters: account number '${validatedValue}'")
private String accountNumber;
#Pattern(regexp = "^[-0-9]*$", message="Fields contain invalid characters: sort code '${validatedValue}'")
#NotBlank
private String sortCode;
public AccountDetails() {}
public String getSortCode() {
return sortCode;
}
public void setSortCode(String sortCode) {
this.sortCode = sortCode;
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
}
#ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status, WebRequest request) {
String errorMessage = ex.getBindingResult()
.getFieldErrors()
.stream()
.findFirst()
.map(FieldError::getDefaultMessage)
.get();
final BankDetailsValidationModel validationResult = new BankDetailsValidationModel(false, false, errorMessage, "");
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(validationResult);
}
}
This can be done by defining a custom validation at the class level, rather than at the field level.
An example:
You have a Person class containing the fields birthYear and deathYear. The business rule requires the birthYear to be non-null if the deathYear is non-null. Otherwise, both fields can be null; or the deathYear can be null if the birthYear is not null.
In this example, we have a relationship between the two fields - in your case, you do not. But that does not change the basic approach: there is one validation, with one message, summarizing the state of both fields.
The Person class:
#FirstOrBoth(firstField = "year of birth", secondField = "year of death", message = ValidationHandler.FIRST_OR_BOTH_MESSAGE)
public class Person {
private Integer birthYear;
private Integer deathYear;
[...the rest of the class...]
}
The #FirstOrBoth Annotation
The above class uses a custom annotation: #FirstOrBoth:
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target(TYPE)
#Retention(RUNTIME)
#Constraint(validatedBy = FirstOrBothValidator.class)
public #interface FirstOrBoth {
String message() default "Please provide values in both fields, or only in the first (or leave both blank).";
String firstField();
String secondField();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The FirstOrBothValidator Class
The above annotation uses a custom validator:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class FirstOrBothValidator implements ConstraintValidator<FirstOrBoth, Object> {
#Override
public boolean isValid(Object obj, ConstraintValidatorContext cvc) {
Person person = (Person) obj;
// whatever validation logic you need goes here
// this is just for illustration:
return !(person.getBirthYear() == null && person.getDeathYear() != null);
}
}
As you already know, you can insert data values into your validation message using placeholders.
Final note: You mention that you can’t change the format of the error messages.
This approach assumes you can at least concatenate the two messages into the single message for the class-level validation.
Related
I have a list that is filled with a request body. I expect 400 BAD Request response status when No value or Null is passed in request.
is working as expected when No value is being passed. But for Null, it does not throw 400. How can I make it work?
class data{
#NotEmpty
private List<#Valid String> values;
}
Request body 1 -> getting response status 200. This is expected.
{
"values": [
"randomValue"
]
}
Request body 2 -> getting response status 400 (VALIDATION_ERROR) . This is expected.
{
}
Request body 3 -> getting response status 400 (VALIDATION_ERROR) . This is expected.
{
"values": [
]
}
Request body 4 -> getting response status 200. Expected status 400 (VALIDATION_ERROR).
{
"values": [
null
]
}
This is because an array/list with null elements is not empty. You can handle this by defining a new custom validation for validating the list input. See an example below:
Define a new Annotation ValidList (you can name it something else too as per your liking). Note the validatedBy attribute - this is the class that will do the actual validation for the fields annotated with this annotation.
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.PARAMETER,
ElementType.ANNOTATION_TYPE})
#Constraint(validatedBy = ListValidator.class)
public #interface ValidList {
String message() default "Array/List field cannot be null";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Define the actual validator class - validation is handled by this custom validator ListValidator (code below):
import java.util.List;
import java.util.Objects;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class ListValidator implements ConstraintValidator<ValidList, List<? extends Object>> {
#Override
public boolean isValid(List<? extends Object> list,
ConstraintValidatorContext context) {
//NOTE: this condition will mark the list invalid even if there is a single null element present in the list (you can change it to fit your use-case)
return !(list == null || list.isEmpty() || list.stream().anyMatch(Objects::isNull));
}
#Override
public void initialize(ValidList constraintAnnotation) {}
}
Now, just use the newly created ValidList annotation on your Data class:
public class Data {
#ValidList
private List<#Valid String> values;
public List<String> getValues() {
return values;
}
public void setValues(List<String> values) {
this.values = values;
}
public Data() {
}
public Data(#NotEmpty List<#Valid String> values) {
this.values = values;
}
}
You can use the #NotNull annotation for list elements:
#NotEmpty
private List<#NotNull String> values;
EDIT:
An example of Controller method:
#GetMapping
public List<String> get(#RequestBody #Valid Data data) {
return data.getValues();
}
Given a POJO in Spring Boot with several dozen fields of type String which is deserialized by Jackson. For demonstration purposes the following example only contains three fields:
#NoArgsConstructor
public class SomeRequest {
#JsonProperty("field_1")
private String field1;
#JsonProperty("field_2")
private String field2;
#JsonProperty("field_3")
private String field3;
}
I'm looking for a way to override the setter method but only for certain fields, i.e. I'd like to avoid repeating the below code for every affected field. This is doable for a handful number of fields but gets tedious for more than a handful.
public setField2(String field2) {
this.field2 = field2 + "?";
}
My idea was to place an annotation on the field like this:
#NoArgsConstructor
public class SomeRequest {
// ...
#JsonProperty("field_2")
#AppendQuestionMark
private String field2;
// ...
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface AppendQuestionMark {
}
But I'm lacking information on how to "implement" the AppendQuestionMark annotation which would override the field's setter method.
Or am I thinking way too complicated?
You can't change the settermethod's body if that's what you are asking. But you can create a method that will take an object (i.e. SomeRequest) as input and check which fields have your Annotation and change the values for those fields as you want.
For example, I created an annotation AppendStr.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface AppendStr {
public String str();;
}
Then I created another class 'AppendStrImpl` that will handle the implementation. I used the following code -
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class AppendStrImpl {
public void changeFields(Object object) throws Exception {
Class<?> clazz = object.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
if (field.isAnnotationPresent(AppendStr.class)) {
// get the getter method name from the field name
String fieldName = field.getName();
String getterMethodName =
"get" +
fieldName.substring(0, 1).toUpperCase() +
fieldName.substring(1);
Method getterMethod = clazz.getMethod(getterMethodName);
String returnValue = (String) getterMethod.invoke(object);
String setterMethodName = getterMethodName.substring(0, 1).replace("g", "s")
+ getterMethodName.substring(1);
Method setterMethod = clazz.getMethod(setterMethodName, String.class);
setterMethod.invoke(object, returnValue + getAppendingString(field));
System.out.println((String) getterMethod.invoke(object));
}
}
}
private String getAppendingString(Field field) {
return field.getAnnotation(AppendStr.class)
.str();
}
}
And this is my POJO class -
public class POJO {
#AppendStr(str = "?")
private String filed1;
#AppendStr(str = "!")
private String filed2;
private String filed3;
#AppendStr(str = "+")
private String filed4;
// ... getters and setters
}
Then I called this method from the main method -
POJO pojo = new POJO("a", "b", "c", "d");
AppendStrImpl appendStrImpl = new AppendStrImpl();
try {
appendStrImpl.changeFields(pojo);
} catch (Exception e) {
e.printStackTrace();
}
Now you can make this call with hard coding or you can use #Aspect too if you want.
The github link is here.
Instead of creating a new annotation that appends a question mark to one generic string field in your pojo you can use the already present JsonDeserialize annotation over the string fields you are interested:
#Data
#NoArgsConstructor
public class SomeRequest {
#JsonProperty("field_1")
private String field1;
#JsonProperty("field_2")
//here the custom deserializer appends the question mark character
#JsonDeserialize(using = StringAppendQuestionMarkDeserializer.class)
private String field2;
}
In your spring boot project you can register the custom deserializer with the JsonComponent annotation like below:
#JsonComponent
public class StringAppendQuestionMarkDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
return node.asText() + "?";
}
}
A spring boot test example using the custom deserializer:
#JsonTest
class CorespringApplicationTests {
#Test
void testDeserialize() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SomeRequest request = mapper.readValue("{\"field_1\":\"value1\",\"field_2\":\"value2\"}", SomeRequest.class);
System.out.println(request); //<-- SomeRequest(field1=value1, field2=value2?)
}
}
Something like the following should do the trick:
#Aspect
#Component
public class AppendQuestionMarkAspect {
#Around("#annotation(AppendQuestionMark)")
public Object appendQuestionMark(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] arguments = joinPoint.getArgs();
return joinPoint.proceed(new Object[] {((String) arguments[0]) + "?"});
}
}
Of course, it would be advisable to check that only one argument exists and that it is, in fact, a String. Or you can also define the pointcut as to be applied only to methods starting with set. But the essence of the code is there.
In the domain model object I have the following field:
private TermStatus termStatus;
TermStatus is an enum:
public enum TermStatus {
NONE,
SUCCESS,
FAIL
}
In the DTO, I have the same field as in the domain object. The question is, how can I validate the passed value? If the API client now passes an incorrect string with the enum value as a parameter (for example, nOnE), it will not receive any information about the error, only the status 400 Bad Request. Is it possible to validate it like this, for example, in the case of javax.validation annotations like #NotBlank, #Size, where in case of an error it will at least be clear what it is. There was an idea to make a separate mapping for this, for example "items/1/complete-term" instead of direct enum transmission, so that in this case the server itself would set the SUCCESS value to the termStatus field. But as far as I know, these things don't look very good in REST API, so I need your ideas
Instead of validating enum directly, you could check whether String is valid for specific enum. To achieve such an effect you could create your own enum validation annotation.
#Documented
#Constraint(validatedBy = EnumValidatorConstraint.class)
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#NotNull
public #interface EnumValidator {
Class<? extends Enum<?>> enum();
String message() default "must be any of enum {enum}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then you need to implement validator to check whether String exist as a part of this enum.
public class EnumValidatorConstraint implements ConstraintValidator<EnumValidator, String> {
Set<String> values;
#Override
public void initialize(EnumValidator constraintAnnotation) {
values = Stream.of(constraintAnnotation.enumClass().getEnumConstants())
.map(Enum::name)
.collect(Collectors.toSet());
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return values.contains(value);
}
}
Lastly, you need to annotate your field with #EnumValidator.
#EnumValidator(enum = TermStatus.class)
private String termStatus;
In case of not matching String a MethodArgumentNotValidException will be thrown. Same as for #NotNull or other constraint validation.
Sounds like you need to implement your own response after validation and tell the API client that the data in your received DTO is invalid and return message with the actual received value (nOnE in your case) and maybe the list of your valid values (if that's not gonna be a security concern). Also, I think the ideal http status for your response would be 422 instead of a generic 400 Bad Request.
For your actual validation implementation, I think you can just directly compare the converted value from DTO to ENUM of the data you received from the API client against your ENUM values in the back-end. If equals to any of the ENUM values, then it's a valid request (200) else, 422.
Hope this helps!
You can make a utility method inside your enum like below
private String text;
TermStatus(String text) {
this.text = text;
}
public static TermStatus fromText(String text) {
return Arrays.stream(values())
.filter(bl -> bl.text.equalsIgnoreCase(text))
.findFirst()
.orElse(null);
}
And set value in dto like below
dto.setTermStatus(TermStatus.fromText(passedValue))
if(dto.getTermStatus()== null)
throw new Exception("Your message");
Hope this helps!
You should use String data type for termStatus. Because of client sends String value for this. Then you have to create Custom validation constraints to fix this as below.
ValueOfEnum
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = ValueOfEnumValidator.class)
public #interface ValueOfEnum
{
Class<? extends Enum<?>> enumClass();
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
ValueOfEnumValidator
public class ValueOfEnumValidator implements ConstraintValidator<ValueOfEnum, CharSequence>
{
private List<String> acceptedValues;
#Override
public void initialize(ValueOfEnum annotation)
{
acceptedValues = Stream.of(annotation.enumClass().getEnumConstants())
.map(Enum::name)
.collect(Collectors.toList());
}
#Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context)
{
if (value == null) {
return true;
}
return acceptedValues.contains(value.toString());
}
}
Now you can #ValueOfEnum annotation for your domain model. Then add #Validated annotation in front of your controller class domain object.
#ValueOfEnum(enumClass = TermStatus.class, message = "Invalid Term Status")
private String termStatus;
I have extension of org.springframework.validation.Validator.
public class MyValidator implements Validator {
#Override
public void validate(Object target, Errors errors) {
...
}
}
My goal is to pass more than one target to method.
I don't like idea with overload validate method because it smells as bad code:
validate(Object target1, Object target1, Errors errors) or creating map with needed targets.
It will be good to know better approach regarding this case.
I did not try the following code, but it demonstrates a basic idea how one field of the bean could be verified against the other. Hopefully, it will help you
Let's say you have the following form bean
public class MyForm {
private String id;
private List<String> oldIds;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<String> getOldIds() {
return oldIds;
}
public void setOldIds(List<String> oldIds) {
this.oldIds = oldIds;
}
}
and the id property has to be validated against the oldIds object (if i did understand your requirements correctly). To achieve it your need to create a constraint and mark your bean. So, the first is the constraint interface
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.validation.Constraint;
import javax.validation.Payload;
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyConstraintValidator.class)
#Documented
public #interface MyConstraint {
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] value();
}
next, you need to implement the constraint validator class:
import java.util.List;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.beanutils.PropertyUtils;
public class MyConstraintValidator implements
ConstraintValidator<MyConstraint, Object> {
private String firstAttribute;
private String secondAttribute;
#Override
public void initialize(final MyConstraint constraintAnnotation) {
firstAttribute = constraintAnnotation.value()[0];
secondAttribute = constraintAnnotation.value()[1];
}
#Override
public boolean isValid(final Object object,
final ConstraintValidatorContext constraintContext) {
try {
final String id = (String) PropertyUtils.getProperty(object,
firstAttribute);
List<String> oldIds = (List<String>) PropertyUtils.getProperty(
object, secondAttribute);
// do your validation
return true;
} catch (final Exception e) {
throw new IllegalArgumentException(e);
}
}
}
finally, apply the created constraint to the form bean
#MyConstraint(value = { "id", "oldIds" })
public class MyForm {
// the code
}
For now, your mark your bean with the #Valid annotation from the javax.validation package or feed it to the validator object
We use a target bean which holds all the data which need to be validated. Something like
private static final class ParamsBean {
String id;
List<String> oldIds;
}
Then we simply cast the object. It's the cleanest possible solution imo, as it does not use generic Map or List of unknown objects (though the casting still is not nice).
i faced with a similar situation where i need to pass more arguments to the validate method so i came up with a idea of my own.in my case i wanted a String to be passed to this method
validate method implemented in the following classes CustomValidatorBean, LocalValidatorFactoryBean, OptionalValidatorFactoryBean, SpringValidatorAdapter
I extended the CustomValidatorBean and called the validate method in super class and it is working perfectly
import javax.validation.Validator;`
import org.apache.commons.lang3.StringUtils;`
import org.springframework.validation.Errors;`
importorg.springframework.validation.beanvalidation.CustomValidatorBean;`
public class MyValidator extends CustomValidatorBean {`
public void myvalidate(Object target,Errors errors,String flag,Profile profile)
{
super.validate(target,errors);
if(StringUtils.isEmpty(profile.name())){
errors.rejectValue("name", "NotBlank.profilereg.name", new Object[] { "name" }, "Missing Required Fields");
}
}
}
i have a specific requirement even if it seems to be pretty straightforward.
I have a form for a user (give you a simplify version of it).
public class Form{
#NotEmpty(message = "{empty}")
#Size(min = 3, max = 32, message = "{size}")
private String firstName;
#NotEmpty(message = "{empty}")
#Size(min = 3, max = 32, message = "{size}")
private String lastName;
}
I want both field to be validate independently, fail at first error (only display one error message then).
For exemple, if i send firstName="" and lastName="aa", i want to get empty message for firstName and size message for lastName.
But by doing that i get 2 error messages for firstName (size and empty).
First alternative i found is using a #groupsequences but still cannot validate field independently.
So i created a new annotation :
#Documented
#Constraint(validatedBy = {})
#Retention(RetentionPolicy.RUNTIME)
#NotEmpty(message = "{empty}", groups = Check.firstTry.class)
#Size(min = 5, max = 32, message = "{size}", groups = Check.secondTry.class)
#GroupSequence({ Check.firstTry.class, Check.secondTry.class, Name.class })
public #interface Name {
public abstract String message() default "{empty}";
public abstract Class<?>[] groups() default {};
public abstract Class<?>[] payload() default {};
}
But it still validating every messages.
If i use #ReportAsSingleViolation it will always return the default message.
Did i miss something somewhere, form validation should be pretty easy for this case, it's not a weird case, is it ?
Thank You
Write a custom ConstraintValidator that checks the constraints one-by-one and reports the first one it finds. The (rather large) downside of this is that you end up reimplementing the checking mechanism for each component annotation, but you do get total control over the error messages.
public class MyConstraintValidator implements ConstraintValidator<Name, String> {
private Name annotation;
#Override
public void initialize(MyConstraintAnnotation annotation) {
this.annotation = annotation;
}
#Override
public boolean isValid(String name, ConstraintValidatorContext context) {
if (name == null) {
context.buildConstraintViolationWithTemplate(emptyMessage).addConstraintViolation();
return false;
} else if (name.length() < getMinSize(annotation)) {
context.buildConstraintViolationWithTemplate(tooShortMessage).addConstraintViolation();
return false;
} else if (name.length > getMaxSize(annotation)) {
context.buildConstraintViolationWithTemplate(tooLongMessage).addConstraintViolation();
return false;
} else {
return true;
}
}
}
Maybe if you could get a handle on the ConstraintValidatorFactory and get the ConstraintValidators you need for each composing annotation? Although that exposes you to the internals of the implementation. It's not a perfect answer at all, but it would get what you need.