Samppa Saarela
Samppa Saarela

Reputation: 101

Javax Validation: Constraint Violations for Map

I use Map for localized values with locale as a key and String as value. For required fields I need to check that at least required locales are set - or at least some value is set. I have implemented validation annotation to be used on such Map fields and a corresponding validator. The problem is, how can I report a missing value? The property path that is used in UI to bind field errors/values, goes wrong each time:

// Domain object:
@LocalizationRequired
private Map<Locale, String> field;


// LocalizationRequiredValidator:
public boolean isValid(Map<Locale, String> map, ConstraintValidatorContext context) {
    if (requiredLocales.isEmpty()) {
        // Check that there exists any not null value
    } else {
        context.disableDefaultConstraintViolation();
        boolean valid = true;
        for (Locale requiredLocale : requiredLocales) { 
            if (map.get(requiredLocale) == null) { // e.g. fi
                valid = false;
                context.buildConstraintViolationWithTemplate("LocalizationRequired")
                // These end up in wrong property path:
                // .addNode(requiredLocale) 
                //    --> field.fi
                // .addNode("[" + requiredLocale + "]") 
                //    --> field.[fi]
                // .addNode(null).addNode(requiredLocale).inIterable() 
                //    --> field.fi
                // .addNode(null).addNode(null).inIterable().atKey(requiredLocale)
                //   --> field
                .addConstraintViolation();
            }
        }
        return valid;
    }
}

The correct path for this error is "field[fi]" but it appears I can only access indexed sub properties. In this case the object itself is indexed. I'm using Hibernate Validator.

Upvotes: 4

Views: 5912

Answers (3)

pgreen2
pgreen2

Reputation: 3651

Old post, but I didn't see anyone add an answer for Map. If you do the following:

if (map.get(requiredLocale) == null) { // e.g. fi
  valid = false;
  context.buildConstraintViolationWithTemplate("LocalizationRequired")
         .addPropertyNode("<map value>")
         .inIterable().atKey(requiredLocale)
         .addConstraintViolation();
}

It will result in field[fi].<map value> which is the same property format you would see with

Map<Locale, @NotNull String>

Upvotes: 0

Samppa Saarela
Samppa Saarela

Reputation: 101

I was not able to find a way to report errors for indexed fields on element level. - Has this been overlooked in the spec?

Here's what I did:

Instead of Map, I used an "embeddable" bean with actual fields for all supported locales (e.g. LocalizedString(String fi, String en, etc). Then reported violations like this:

context.buildConstraintViolationWithTemplate("LocalizationRequired")
.addNode(requiredLocale)
.addConstraintViolation();

This is feasible in our case as we have a predefined set of supported languages, but it doesn't scale to indexed fields with arbitrary indexes.

Further more either Spring's LocalValidatorFactoryBean or Hibernate Validator does not properly support validation of embeddables. As the same component is used in different places with different validation requirements, I cannot use @Valid with actual validation annotations within the component itself - at least not without support for validation groups on @Valid.

The problem with Spring's LocalValidatorFactoryBean or Hibernate Validator is that the invalidValue of ConstraintViolation is LocalizedString ("field") instead of the value of the reported erroneous nested field ("field.fi"). Luckily this can be resolved by overriding LocalValidatorFactoryBean.processConstraintViolations by removing that "custom FieldError registration with invalid value from ConstraintViolation" and reporting errors simply by

errors.rejectValue(field, errorCode, errorArgs, violation.getMessage());

This way Spring resolves the invalidValue using given field.

Upvotes: 3

Eugene
Eugene

Reputation: 120868

This is a pretty interesting question. Right now I do not have time to test it myself :( , but this guy over here :

Validation of a Collection

Seems to be able to Validate a Collection of elements. So if you switch to a Collection instead of a Map (which should be fairly easy), for example:

 class LocaleToString {
      private Locale locale;
      private String language;
 }

 @LocalizationRequired
 List<LocaleToString> locales;

You should be able to achieve what you want, I think.

Upvotes: 0

Related Questions