I am trying to use JSR-303 Bean Validation with Hibernate Validator to ensure that a collection does not contain null values.
I know that I can annotate my collection as follows:
#Valid
#NotEmpty
private Set<EmailAddress> emails = new HashSet<>();
This does the job for ensuring that the collection itself is not null or empty and in the case that I add a non-null EmailAddress element it also validates this correctly. However, it doesn't prevent adding a null element.
Is there a way to prevent a null element being added to the collection? In an ideal world the solution would be declarative (like the rest of the validation) and wouldn't involve programmatically iterating through the collection manually doing null checks.
Thanks in advance!
Bean Validation is missing the #NotBlank annotation for Collections that would pretty much fit the scenario you are describing. Basically, as you mentioned, you would require to implement a custom validator that will programatically check that the contents of the collection ensuring that none of the elements inside it are null.
Here is an example of the custom validator you would need:
public class CollectionNotBlankValidator implements
ConstraintValidator<CollectionNotBlank, Collection> {
#Override
public void initialize(CollectionNotBlank constraintAnnotation) {
}
#Override
public boolean isValid(Collection value, ConstraintValidatorContext context) {
Iterator<Object> iter = value.iterator();
while(iter.hasNext()){
Object element = iter.next();
if (element == null)
return false;
}
return true;
}
}
As you can see I have named the custom annotaion CollectionNotBlank. Here is an example of the code for the custom annotation:
#Target(FIELD)
#Retention(RUNTIME)
#Constraint(validatedBy = CollectionNotBlankValidator.class)
#ReportAsSingleViolation
#Documented
public #interface CollectionNotBlank {
String message() default "The elements inside the collection cannot be null values.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Related
I want to force user to send numeric value for a field in request, as user may enter char as well.
Since I haven't found any built in solution in spring mvc validation, I chose to create my own custom validator to check the entered value is number or not.
Please find below code snippet.
Constraint interface :
#Documented
#Constraint(validatedBy = {IntegerValidator.class})
#Target({ METHOD, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
public #interface IntegerConstraint {
String message() default "Please enter numers only...!!!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator class :
public class IntegerValidator implements ConstraintValidator<IntegerConstraint, String> {
#Override
public void initialize(IntegerConstraint contactNumber) {
}
#Override
public boolean isValid(String reqParam, ConstraintValidatorContext cxt) {
return !StringUtils.isEmpty(reqParam) && reqParam.matches("^(0|[1-9][0-9]*)$");
}
}
DTO class field :
#IntegerConstraint
#PositiveOrZero(message = "Sorting number either can be positive or zero...!!!")
private Integer sortOrd;
Controller :
public ModelAndView addDetail(#Valid #ModelAttribute("fooDetails") FooDTO footDTO,
BindingResult result, HttpServletRequest request)
Error log :
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'com.eps.customvalidator.IntegerConstraint' validating type 'java.lang.Integer'. Check configuration for 'sortOrd'
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getExceptionForNullValidator(ConstraintTree.java:108) ~[hibernate-validator-6.0.10.Final.jar:6.0.10.Final]
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getInitializedConstraintValidator(ConstraintTree.java:140) ~[hibernate-validator-6.0.10.Final.jar:6.0.10.Final]
at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:55) ~[hibernate-validator-6.0.10.Final.jar:6.0.10.Final]
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:73) ~[hibernate-validator-6.0.10.Final.jar:6.0.10.Final]
This might happened because your IntegerValidator is implementing ConstraintValidator<IntegerConstraint, String>, thus it is expected to validate String fields. But you are applying it on an Integer field.
You should either consider to change ConstraintValidator<IntegerConstraint, String> to ConstraintValidator<IntegerConstraint, Integer> or to change your DTO to private String sortOrd;
For example, I have got a class:
#Getter
#Setter
class Notification {
private String recipient;
private Channel channel;
enum Channel {
SMS, EMAIL
}
}
I could define my own Validator, for instance:
#Target(TYPE)
#Retention(RUNTIME)
#Constraint(validatedBy = {RecipientValidator.class})
#interface ValidRecipient {
// required arguments of validation annotation
}
class RecipientValidator implements ConstraintValidator<ValidRecipient, Notification> {
#Override
public void initialize(ValidRecipient annotation) {
}
#Override
public boolean isValid(Notification value, ConstraintValidatorContext context) {
boolean result = true;
if (value.getChannel() == SMS) {
return matches(value.getRecipient(), "<phone-number-regexp>");
}
if (value.getChannel() == EMAIL) {
// can I reuse Hibernate's Email Validation there?
return matches(value.getRecipient(), "<email-regexp>");
}
return result;
}
}
Of course I can google regexp of email and copy-paste there but Hibernate's Bean Validation implementation already has the Email Validation (under #Email annotation).
Is there a way to reuse that validation implementation in my custom validator?
There is no official way to reuse a validator in another one.
What you can do though would be to initialize an EmailValidator attribute in initialize() and call its isValid() method in your isValid() method.
Keep in mind that EmailValidator is internal so it might be subject to changes in the future.
I already know how to add annotation based validation on specific attributes in Entity class like :-
public class Person {
#NotNull
private String firstName;
private String lastName;
//...
}
But is it possible to add annotation on class Person, in order to validate all the attributes inside this class, by creating a Customised Validation Class and handling validation there somewhere like :-
#Retention(value = RetentionPolicy.RUNTIME)
#Target(value = ElementType.METHOD)
public #interface PersonneName {
public String firstName();
}
I am working on a project to get Constraints from Database and creating Customised Validation Class and applying on the Entity class attributes according to the constaints got from DB.
Please suggest.
Yes, of course, it's possible. First, create the definition of your annotation. Pretty much like you did in your example, however, with a different #Target type
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = PersonValidator.class)
public #interface ValidPerson {
String message () default "Your custom message";
Class<?>[] groups () default {};
Class<? extends Payload>[] payload () default {};
}
Then implement the validator whose isValid method takes the instance of your Person class:
public class PersonValidator implements ConstraintValidator<ValidPerson, Person> {
#Override
public boolean isValid (Person person, ConstraintValidatorContext context) {
// your validation logic
}
}
Sure it is possible, just check the documentation regarding how to write custom class level constraints - http://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-class-level-constraints
The important thing of course is that you make sure that one can actually place the constraint annotation on the type level. For that you need to add ElementType.TYPE to the #Target annotation.
I'm attempting to create a class level JSR-303 validation definition that checks that one property occurs before another in time. Because the this validation only makes sense for Calendar properties I was wondering if it is possible to test the property type in the initialize method.
My annotation definition is:
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = TemporalSequenceValidator.class)
#Documented
public #interface TemporalSequence {
String message() default "{uk.co.zodiac2000.vcms.constraints.TemporalSequence}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String first();
String second();
}
and the validator implementation:
public class TemporalSequenceValidator implements
ConstraintValidator<TemporalSequence, Object> {
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(final TemporalSequence constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
// Is it possible to test type of firstFieldName and
// secondFieldName properties here?
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
// omitted
}
}
Is this a sensible thing to do? What approach would you suggest I use if it is? And what action should occur if the properties are not of the correct type?
You can't really do the check in initialize() since you can't access the validated object there. Instead you could check the type of the fields of the validated object in isValid() using reflection:
if ( !Calendar.class.isAssignableFrom(
value.getClass().getField( firstFieldName ).getType() ) ) {
throw new ValidationException( "Field " + firstFieldName + " is not of type Calendar." );
}
Problem
I have a Spring MVC application that requires me to translate the id's and names of a list of a certain entity to an array of JSON objects with specific formatting, and output that on a certain request. That is, I need an array of JSON objects like this:
{
label: Subject.getId()
value: Subject.getName()
}
For easy use with the jQuery Autocomplete plugin.
So in my controller, I wrote the following:
#RequestMapping(value = "/autocomplete.json", method = RequestMethod.GET)
#JsonSerialize(contentUsing=SubjectAutocompleteSerializer.class)
public #ResponseBody List<Subject> autocompleteJson() {
return Subject.findAllSubjects();
}
// Internal class
public class SubjectAutocompleteSerializer extends JsonSerializer<Subject> {
#Override
public void serialize(Subject value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("label", value.getId().toString());
jgen.writeStringField("value", value.getName());
jgen.writeEndObject();
}
}
The JSON I get back however, is the default serialization inferred by Jackson. My custom serializer seems to be completely ignored. Obviously the problem is incorrect usage of #JsonSerialize or JsonSerializer, but I could not find proper usage of these within context anywhere.
Question
What is the proper way to use Jackson to achieve the serialization I want? Please note that it's important that the entities are only serialized this way in this context, and open to other serialization elsewhere
#JsonSerialize should be set on the class that's being serialized not the controller.
#JsonSerialize should be set on the class that's being serialized not the controller.
I'd like to add my two cents (a use case example) to the above answer... You can't always specify a json serializer for a particular type especially if this is a generic type (erasure doesn't allow to pick the the serializer for a particular generic at runtime), however you can always create a new type (you can extend the generalized type or create a wrapper if the serialized type is final and can't be extended) and custom JsonSerializer for that type. For example you can do something like this to serialize different org.springframework.data.domain.Page types:
#JsonComponent
public class PageOfMyDtosSerializer
extends JsonSerializer<Page<MyDto>> {
#Override
public void serialize(Page<MyDto> page,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException {
//...serialization logic for Page<MyDto> type
}
}
#JsonSerialize(using = PageOfMyDtosSerializer.class)
public class PageOfMyDtos extends PageImpl<MyDto> {
public PageOfMyDtos(List<MyDto> content, Pageable pageable, long total) {
super(content, pageable, total);
}
}
And then you can return your type from methods of your services - the necessary serializer will be utilized unambiguously:
#Service
#Transactional
public class MyServiceImpl implements MyService {
...
#Override
public Page<UserProfileDto> searchForUsers(
Pageable pageable,
SearchCriteriaDto criteriaDto) {
//...some business logic
/*here you pass the necessary search Specification or something else...*/
final Page<Entity> entities = myEntityRepository.findAll(...);
/*here you goes the conversion logic of your choice...*/
final List<MyDto> content = modelMapper.map(entieis.getContent(), new TypeToken<List<MyDto>>(){}.getType());
/*and finally return your the your new type so it will be serialized with the jsonSerializer we have specified*/
return new PageOfMyDtos(content, pageable, entities.getTotalElements());
}
}