Skip to content
RichardHightower edited this page Feb 16, 2014 · 2 revisions

I added some examples of validation support. I pulled the validation support from Crank. :)

I have an example using Annotations and an example using an validation meta data reader.

The class that kicks things off is the RecursiveDescentPropertyValidator.

It recursively walks an object's properties looking for properties that have validation annotations or rules.

The framework was originally written for J-S-F (but no longer has any ties to J-S-F) and predates all other property validation frameworks that are similar (Seam, Spring and the standard Java EE).

(I write J-S-F like that because I want no association with it for this project.)

Later the framework was ported to work Spring MVC.

The framework was never meant to be used outside of those environments so I had to tweak it a bit to make it useable w/o Spring or J-S-F. Since it was meant to work with both the higher level abstractions were already there so I just I had to tweak it to make it work without Spring and J-S-F.

That said... It is not quite a fluent API, but the basics are there and it could be a fluent API with a little elbow grease. Also if you combined it with the Criteria API, then you could have some really powerful validation rules.

The annotation processing is special in that it will read your annotations and not just its. This is the only way that I will support annotations... EVER! It treats annotation like meta data, and it can read any meta data. It can also read the metadata from any meta data source (just an interface).

There is an annotation metadata reader and a java util properties meta data reader.

Basically you can use validators like follows:

    @Test
    public void testRequired() {

        RequiredValidator required = Validators.required("phone number required");


        ValidatorMessage message = (ValidatorMessage) required.validate(null, "Phone Number");


        boolean ok = true;

        ok |= message.hasError() || die("Phone number is required");


        message = (ValidatorMessage) required.validate("", "Phone Number");


        //Empty strings don't count!
        ok |= message.hasError() || die("Phone number is required");


    }

But the above is not very useful. What you want is to group a bunch of validators into one composite validator.

    @Test
    public void testComposite() {


        CompositeValidator validators = Validators.validators(required("phone number required"),
                length(7, 12, "phone number must be 7 to  12 characters long"));


        ValidatorMessages messages = (ValidatorMessages) validators.validate(null, "Phone Number");


        boolean ok = true;

        ok |= messages.hasError() || die("required");


        messages = (ValidatorMessages) validators.validate("123", "Phone Number");

        ok |= messages.hasError() || die("wrong length");


        messages = (ValidatorMessages) validators.validate("1231234567", "Phone Number");

        ok |= !messages.hasError() || die("all good now");


    }

But what you really want is to just walk an object and grab meta data and validators and just execute the validation rules:

    public static class Employee {
        String firstName;
        int age;
        String phone;

        public Employee(String name, int age, String phone) {
            this.firstName = name;
            this.age = age;
            this.phone = phone;
        }

        public String getFirstName() {
            return firstName;
        }

        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getPhone() {
            return phone;
        }

        public void setPhone(String phone) {
            this.phone = phone;
        }
    }

Here I just implement my own validator meta data reader on the fly...

    class ValidatorReader implements ValidatorMetaDataReader {


        @Override
        public List<ValidatorMetaData> readMetaData(Class<?> clazz, String propertyName) {
                if ( classToRulesMap.get(clazz).get(propertyName) == null) {
                    return Collections.EMPTY_LIST;
                } else {
                    return classToRulesMap.get(clazz).get(propertyName);
                }

        }
    }

Then I can apply a whole gaggle of validator rules to an object:

    @Test
    public void testRecursive() {


        Map<String, Object> objectMap = map(
                "/org/boon/validator/length" , (Object )new LengthValidator(),
                "/org/boon/validator/personName" , Validators.personName("", "")
        );



        RecursiveDescentPropertyValidator validator = new RecursiveDescentPropertyValidator();

        validator.setValidatorMetaDataReader( new ValidatorReader()  );

        List<RecursiveDescentPropertyValidator.MessageHolder> messageHolders = Collections.EMPTY_LIST;


        messageHolders  = validator.validateObject(new Employee("Rick", 43, "555-121-3333"), objectMap);



        int errors = 0;

        for (RecursiveDescentPropertyValidator.MessageHolder messageHolder : messageHolders) {

            puts(messageHolder.propertyPath);

            puts(messageHolder.holder.hasError());


            if (messageHolder.holder.hasError()) {
                errors ++;
            }

        }

        if ( errors > 0 ) {
             die (" Not expecting any errors ");
        }




        messageHolders  = validator.validateObject(new Employee("123", 50, "A"), objectMap);

        errors = 0;

        for (RecursiveDescentPropertyValidator.MessageHolder messageHolder : messageHolders) {

            puts(messageHolder.propertyPath);

            puts(messageHolder.holder.hasError());


            if (messageHolder.holder.hasError()) {
                errors ++;
            }

        }

        if ( errors != 2 ) {
            die (" expecting two errors " + errors );
        }

    }

I also have the option of using annotations which is quite easy:

    public static class Employee2 {


        @ProperNoun (detailMessage = "First Name must be a proper noun")
        String firstName;
        int age;


        @Length(max = 12, min=5, detailMessage = "Phone number must be a phone number")
        String phone;

        public Employee2(String name, int age, String phone) {
            this.firstName = name;
            this.age = age;
            this.phone = phone;
        }

        public String getFirstName() {
            return firstName;
        }

        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getPhone() {
            return phone;
        }

        public void setPhone(String phone) {
            this.phone = phone;
        }
    }

The test is pretty much the same

    @Test
    public void testRecursiveWithAnnotations() {


        Map<String, Object> objectMap = map(
                "/org/boon/validator/length" , (Object )new LengthValidator(),
                "/org/boon/validator/properNoun" , Validators.properNoun("", "")
        );



        RecursiveDescentPropertyValidator validator = new RecursiveDescentPropertyValidator();


        List<RecursiveDescentPropertyValidator.MessageHolder> messageHolders = Collections.EMPTY_LIST;


        messageHolders  = validator.validateObject(new Employee2("Rick", 43, "555-121-3333"), objectMap);



        int errors = 0;

        for (RecursiveDescentPropertyValidator.MessageHolder messageHolder : messageHolders) {

            puts(messageHolder.propertyPath);

            puts(messageHolder.holder.hasError());


            if (messageHolder.holder.hasError()) {
                errors ++;
            }

        }

        if ( errors > 0 ) {
            die (" Not expecting any errors ");
        }




        messageHolders  = validator.validateObject(new Employee2("123", 50, "A"), objectMap);

        errors = 0;

        for (RecursiveDescentPropertyValidator.MessageHolder messageHolder : messageHolders) {

            puts(messageHolder.propertyPath);

            puts(messageHolder.holder.hasError());


            if (messageHolder.holder.hasError()) {
                errors ++;
            }

        }

        if ( errors != 2 ) {
            die (" expecting two errors " + errors );
        }

    }

The trick with annotations is it uses a white list of which package to use. So you have to add your package that contains your annotations to the white list.

public class AnnotationValidatorMetaDataReader implements ValidatorMetaDataReader, Serializable {
   
        /** Holds a cache of meta-data to reduce parsing with regex and to avoid
         * reflection.
         * Since this could get hit by multiple threads.
         * */
        private  Map<String, List<ValidatorMetaData>> metaDataCache =
           new ConcurrentHashMap<>();
   
        /** Holds a list of packages that contain annotations that we will process.
         * If the annotation package is not in this list, it will not be processed.
         */
        private Set<String> validationAnnotationPackages = new HashSet<>();
        {
            /* By default, we only process our own annotations. */
            validationAnnotationPackages.add("org.boon.validation.annotations");
        }

Thoughts

Thoughts? Write me at richard high tower AT g mail dot c-o-m (Rick Hightower).

Further Reading:

If you are new to boon start here:

Why Boon?

Easily read in files into lines or a giant string with one method call. Works with files, URLs, class-path, etc. Boon IO support will surprise you how easy it is. Boon has Slice notation for dealing with Strings, Lists, primitive arrays, Tree Maps, etc. If you are from Groovy land, Ruby land, Python land, or whatever land, and you have to use Java then Boon might give you some relief from API bloat. If you are like me, and you like to use Java, then Boon is for you too. Boon lets Java be Java, but adds the missing productive APIs from Python, Ruby, and Groovy. Boon may not be Ruby or Groovy, but its a real Boon to Java development.

Core Boon Philosophy

Core Boon will never have any dependencies. It will always be able to run as a single jar. This is not just NIH, but it is partly. My view of what Java needs is more inline with what Python, Ruby and Groovy provide. Boon is an addition on top of the JVM to make up the difference between the harder to use APIs that come with Java and the types of utilities that are built into Ruby, Python, PHP, Groovy etc. Boon is a Java centric view of those libs. The vision of Boon and the current implementation is really far apart.

===

Contact Info

blog|[twitter](https://twitter.com/RickHigh|[infoq]http://www.infoq.com/author/Rick-Hightower|[stackoverflow](http://stackoverflow.com/users/2876739/rickhigh)|[java lobby](http://java.dzone.com/users/rhightower)|Other | richard high tower AT g mail dot c-o-m (Rick Hightower)|work|cloud|nosql

Clone this wiki locally