Search code examples
javascriptjavavalidationgroovyoval

OVal activation rules for constraints not working


I'm trying to implement activation rules for OVal following their documentation but seem to be running into issues with it finding the variable I'm using for the comparison. Unfortunately other than the small section in their documentation there isn't much online about the topic.

The other part of the issue I'm trying to solve is to also get this working for constructor validation using the @Guarded annotation. This works fine without the constraint rules as described in my answer to this question, but not when I add activation rules in either JavaScript or Groovy.

3.4. Declaring activation rules for constraints

public class BusinessObject
{
    private String fieldA;

    @NotNull(when = "groovy:_this.fieldA != null")
    private String fieldB;
}

I've tried both JS and groovy and tried with and without the _this. Removing it results in: ReferenceError: "someString" is not defined So I assume the way they list in the docs is correct but I'm missing something.

Code for field validation:

public class BusinessObject {
    private String fieldA;

    //@NotNull(when = "groovy:_this.fieldA != null") //works for public & private
    @NotNull(when = "javascript:_this.fieldA != null") //only works when fieldA is public
    private String fieldB;

    public BusinessObject(){}

    public BusinessObject(String fieldA, String fieldB) {
        this.fieldA = fieldA;
        this.fieldB = fieldB;
    }
}

Code for constructor validation:

@Guarded
public class BusinessObjectConstructorValidation {
    private String fieldA;
    private String fieldB;

    public BusinessObjectConstructorValidation(
            String fieldA,
            @NotNull(when = "groovy:_this.fieldA != null") String fieldB) {

        this.fieldA = fieldA;
        this.fieldB = fieldB;
    }
}

How I'm testing the object:

public class BusinessObjectTest {

    @Test
    public void fieldANullFieldBNotValidatedNoViolations() {
        BusinessObject businessObject = new BusinessObject(null, null);
        Validator validator = new Validator();
        validator.validate(businessObject);
    }

    //This test will fail if the fields are private and using javascript
    //If it's public or using groovy it passes
    @Test
    public void fieldANotNullFieldBValidatedViolationsSizeIsOne() {
        BusinessObject businessObject = new BusinessObject("A", null);
        Validator validator = new Validator();
        List<ConstraintViolation> errors = validator.validate(businessObject);
        System.out.println(errors.size());
        assertThat(errors.size(), is(1));
    }

    @Test
    public void fieldANullFieldBNotNullNoViolations() {
        BusinessObject businessObject = new BusinessObject(null, "B");
        Validator validator = new Validator();
        validator.validate(businessObject);
    }
}

I'm not sure why the JavaScript version behaves differently to the groovy one, have tried changing all combinations I could think of including: _this.fieldA, __this.fieldA, window.fieldA, fieldA and __fieldA

UPDATE The JavaScript seems to work for private fields as long as it has a public getter.


Solution

  • I've solved the issue by doing the following and switching to groovy instead of JavaScript as suggested by the developer.

    The JavaScript Engine Rhino cannot directly access private fields. E.g. something like "javascript:_this.myPrivateField != null && _this.myPrivateField.length > 10" will always return false, no matter the value of the private field. Rhino apparently does not raise an exception if it cannot access the private field.

    I would recommend you to use groovy instead of javascript for constraint activation. For most scripting runtimes (including JavaScript) the statement _this.someVariable will result in the invocation of the _this.getSomeVariable() getter and not in a direct access of a private field with the same name.

    In the first line of my code example the (checkInvariants = false) is required if the object you're validating has any public getter methods otherwise it will result in a StackOverflowError

    Unfortunately adding the validation in the constructor as I posted in my question doesn't work. So to get around this I needed to add the validation to the field and add the @PostValidateThis annotation to the constructor.

    Example Pojo using validation after the constructor is called.

    @Guarded(checkInvariants = false)// removing this results in StackOverflowError
    public class User {
        private final String firstName;
        private final String lastName;
        @NotNull(when = "groovy:_this.lastName != null")
        private final Integer age;
    
        @PostValidateThis
        public User(String firstName, String lastName, Integer age) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.age = age;
        }
    
        public String getFirstName() {
            return firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public Integer getAge() {
            return age;
        }
    }
    

    Basic unit test for the above pojo.

    public class UserTest {
        @Test
        public void userValidParamsNoException() throws Exception {
            User user = new User("foo","bar",123);
            assertThat(user, is(not(nullValue())));
            assertThat(user.getFirstName(), is("foo"));
            assertThat(user.getLastName(), is("bar"));
            assertThat(user.getAge(), is(123));
        }
    
        @Test
        public void userLastNameNullNoException() throws Exception {
            User user = new User("foo",null, null);
            assertThat(user, is(not(nullValue())));
            assertThat(user.getFirstName(), is("foo"));
        }
    
        @Test(expected = ConstraintsViolatedException.class)
        public void userLastNameNotNullAgeNullThrowsException() throws Exception {
            User user = new User("foo","bar", null);
        }
    }