Friday, February 15, 2013

Javascript accessing JSR303 or custom validation annotations

Wouldn't it be great to have all JSR-303, custom validation annotations, hibernate specific validations, type and so on accessible from the front end?
 
var clientValidations = ${ju:getValidations(client)};


Here is a new method for the JSONUtils.java I have been working on to support a richer application:
 
...
    /**
     * Given a target object it returns all fields and annotations for those related to validations: Map<String fieldName,
     * Map<String annotationName, Map<String annotationAttributeName, String annotationAttributeValue>>
     * 
     * @param target
     * @return
     * @throws IOException
     * @throws JsonMappingException
     * @throws JsonGenerationException
     */
    public static String getValidations(Object target) throws JsonGenerationException, JsonMappingException, IOException {
        Map<String, Map<String, Map<String, Object>>> validations = new HashMap<String, Map<String, Map<String, Object>>>();
        List<String> validationPackages = new ArrayList<String>();

        // JSR 303
        validationPackages.add("javax.validation.constraints");
        // Hibernate
        validationPackages.add("org.hibernate.validator.constraints");
        // Custom
        validationPackages.add("com.sample.validator.constraints");

        Field[] fields = target.getClass().getDeclaredFields();
        for (Field field : fields) {
            boolean accessible = field.isAccessible();

            if (!accessible) {
                field.setAccessible(true);
            }
            Annotation[] annotations = field.getAnnotations();
            Map<String, Map<String, Object>> validationsMap = new HashMap<String, Map<String, Object>>();
            for (Annotation annotation : annotations) {
                if (validationPackages.contains(annotation.annotationType().getPackage().getName())) {
                    String annotationName = annotation.annotationType().getName();
                    Map<String, Object> annotationAtributes = AnnotationUtils.getAnnotationAttributes(annotation);
                    Iterator<String> it = annotationAtributes.keySet().iterator();
                    while (it.hasNext()) {
                        String key = it.next();
                        // Remove unneeded attributes
                        if ("groups".equals(key) || "payload".equals(key)) {
                            it.remove();
                        }
                    }
                    validationsMap.put(annotationName, annotationAtributes);
                }
            }
            Map<String, Object> typeMap = new HashMap<String, Object>();
            typeMap.put(TYPE, field.getType());
            validationsMap.put(TYPE, typeMap);
            if (validationsMap.size() > 0) {
                validations.put(field.getName(), validationsMap);
            }
        }
        return toJson(validations);
    }
...
And then the JSONUtils.tld declaration:
 
<function>
        <name>getValidations</name>
        <function-class>com.sample.utils.JsonUtils</function-class>
        <function-signature>
            String getValidations(java.lang.Object)
        </function-signature>
    </function>
Here is an example of such JSON generated file. As you can see there are JSR-303, hibernate scpecific and custom validation constraints listed. Time for the Javascript magic!
 
{
   "privateClientId":{
      "com.sample.validator.constraints.PrivateClientId":{
         "message":"validation.privateClientId"
      }
   },
   "sampleDistributionEmailAddress":{
      "javax.validation.constraints.Size":{
         "min":0,
         "max":50,
         "message":"{javax.validation.constraints.Size.message}"
      },
      "org.hibernate.validator.constraints.Email":{
         "message":"validation.email"
      }
   },
   "name":{
      "org.hibernate.validator.constraints.NotEmpty":{
         "message":"validation.mandatoryField"
      }
   },
   "number":{
      "javax.validation.constraints.Pattern":{
         "flags":[

         ],
         "message":"validation.client.number",
         "regexp":"^\\d{8}$"
      }
   },
   "group":{
      "javax.validation.constraints.NotNull":{
         "message":"validation.mandatoryField"
      }
   },
   "difficultyLevel":{
      "org.hibernate.validator.constraints.Range":{
         "min":1,
         "max":5,
         "message":"{org.hibernate.validator.constraints.Range.message}"
      }
   }
   "numberOfEmployees":{
     "type":{
       "type":"java.lang.Integer"
     }
   }
}

1 comment:

gongiskhan said...

Hi Nestor,

Thanks for sharing this it will already be a great help.

I was wondering if you go to do something on the JavaScript side (as in implementing the validation rules)?

I am planning to start doing this myself so any piece of code you have around would be appreciated. Even if specific or very incomplete.
If you want I can post the results here after its completed. I'm sure it should help someone else.

Cheers,
Goncalo

Followers