JSR 303 - Bean Validation Spec

JSR 303 is the Spec dealing with Bean Validation

Two important concepts

  • API provided by the JEE lib - javax.validation
  • IMPL provided by others (similar to other specs). In this case the reference implementation is the Hibernate validator and is used extensively (e.g. D-API)

Bean Validation can be done by

  • Annotations directly on the Bean class itself (most popular way :))
  • xml validations file -> useful when no control over bean class you want to validate.
  • Programmatic addition of constraints (might be Hibernate specific though ….. )

Example of directly calling the Bean Validation http://stackoverflow.com/questions/19190592/manually-call-spring-annotation-validation

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

Driver driver = new Driver();
driver.setAge(16);
Car porsche = new Car();
driver.setCar(porsche);

Set<ConstraintViolation<Driver>> violations = validator.validate(driver);

Future Developments:

  • BeanValidation 1.0 - JSR 303 is the first spec and is quite old. (2009)
  • BeanValidation 1.1 - JSR 349 - already in JEE7 - method-level validation, CDI integration (2013)
  • BeanValidation 2.0 - JSR 380 - planned for JEE8 - allow use of Java8 language features.

Useful Links:

Errors that can happen if you don't have a provider jar available:

Unable to create a Configuration, because no Bean Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath

Hibernate validator

http://stackoverflow.com/questions/30521821/hibernate-validation-with-custom-messages-in-property-file

Bean Validation API

Fun with reflection and getting Bean Validation API Payloads

Getting the Payload from the Bean Validation definition

@Test

    public void throwaway() throws Exception {
      TestBean test = new TestBean();
      test.test = null;

      Set<ConstraintViolation<TestBean>> violations = validator.validate(test);
      for (ConstraintViolation<TestBean> violation : violations) {
        ConstraintDescriptor<?> constraintDescriptor = violation.getConstraintDescriptor();

        for (Class<? extends Payload> payload : constraintDescriptor.getPayload()) {
          payload.getClass();

          // Cast to a subclass and then get the fields
          CannedMessageEnum msg = (CannedMessageEnum)
            payload.asSubclass(MissingMandatoryData.class).getFields()[0].get(payload);

          msg.getMsgId();

          // Don't bother with cast and get the field by name instead of index.
          CannedMessageEnum msg2 = (CannedMessageEnum)payload.getField("msg").get(payload);
          msg2.getMsgId();

          Assert.assertEquals(CannedMessageEnum.MANDATORY_DATA_MISSING.getMsgId(), msg2.getMsgId());
        }
      }
      assertFalse(violations.isEmpty());

    }

    public class TestBean {

      @NotNull(message = "Name is mandatory",
               payload = MandatoryData.class)
      String test;

      @NotNull(message = "Name is mandatory",
               payload = DCTest.class)
      String testNonExistantCannedMsg;
    }

    public interface DCTest extends Payload {

      CannedMessageEnum msg = CannedMessageEnum.NOT_EXISTING_MSG;
    }

Getting the QueryParam "value" attribute to see the query parameter input field value


    static final String QUERY_PARAM_SOURCE = "value";

    /**
     * Look for QueryParam annotation and retrieve its "value" property as the source e.g. @QueryParam("origin")String
     * origin;
     *
     * @param annotations
     * @return String - parameter value to use as the issue source. null if the parameter could not be determined from the
     *         annotations
     */
    public String getSourceParameter(Annotation[] annotations) {
      String parameter = null;

      if (annotations.length == 1) {
        Annotation annotation = annotations[0];
        if (annotation.annotationType() == javax.ws.rs.QueryParam.class) {
          try {
            parameter = annotation.annotationType().getMethod(QUERY_PARAM_SOURCE).invoke(annotation).toString();
          } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException | IllegalArgumentException
                   | SecurityException e) {
            logger.warn("Query Parameter Value could not be determined, default value null will be used. ", e);
            parameter = null;
          }
        }
      }

      return parameter;
    }