You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bval.apache.org by mb...@apache.org on 2010/10/06 23:18:01 UTC

svn commit: r1005258 - in /incubator/bval/sandbox/lang3-work/bval-jsr303d/src: main/java/org/apache/bval/constraints/dynamic/NullValue.java test/java/org/apache/bval/constraints/dynamic/NullValueValidationTest.java

Author: mbenson
Date: Wed Oct  6 21:18:01 2010
New Revision: 1005258

URL: http://svn.apache.org/viewvc?rev=1005258&view=rev
Log:
dynamic replacement for Null/NotNull: NullValue

Added:
    incubator/bval/sandbox/lang3-work/bval-jsr303d/src/main/java/org/apache/bval/constraints/dynamic/NullValue.java   (with props)
    incubator/bval/sandbox/lang3-work/bval-jsr303d/src/test/java/org/apache/bval/constraints/dynamic/NullValueValidationTest.java   (with props)

Added: incubator/bval/sandbox/lang3-work/bval-jsr303d/src/main/java/org/apache/bval/constraints/dynamic/NullValue.java
URL: http://svn.apache.org/viewvc/incubator/bval/sandbox/lang3-work/bval-jsr303d/src/main/java/org/apache/bval/constraints/dynamic/NullValue.java?rev=1005258&view=auto
==============================================================================
--- incubator/bval/sandbox/lang3-work/bval-jsr303d/src/main/java/org/apache/bval/constraints/dynamic/NullValue.java (added)
+++ incubator/bval/sandbox/lang3-work/bval-jsr303d/src/main/java/org/apache/bval/constraints/dynamic/NullValue.java Wed Oct  6 21:18:01 2010
@@ -0,0 +1,269 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.bval.constraints.dynamic;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.validation.Constraint;
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import javax.validation.Payload;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Null;
+
+import org.apache.bval.jsr303.dynamic.ConstraintAppender;
+import org.apache.bval.jsr303.groups.Group;
+import org.apache.bval.jsr303.groups.Groups;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.proxy2.stub.AnnotationFactory;
+import org.apache.commons.proxy2.stub.StubConfigurer;
+
+/**
+ * Merges {@link Null} and {@link NotNull} with dynamic handling.
+ * 
+ * @version $Rev$ $Date$
+ */
+@Documented
+@Target({ METHOD, FIELD, ANNOTATION_TYPE, PARAMETER })
+@Retention(RUNTIME)
+@Constraint(validatedBy = { NullValue.Validator.class })
+@ConstraintAppender.Use(NullValue.Appender.class)
+public @interface NullValue {
+    /**
+     * The behavior of this NullValue constraint. Defaults to {@link Rule#REQUIRE} for semantic reasons; i.e.
+     * "NullValue" by itself implies a null value.
+     * 
+     * @return Rule
+     */
+    Rule value() default Rule.REQUIRE;
+
+    String message() default "{org.apache.bval.constraints.dynamic.NullValue}";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+
+    /**
+     * Multivalued {@link NullValue}.
+     */
+    @Documented
+    @Target({ METHOD, FIELD, ANNOTATION_TYPE, PARAMETER })
+    @Retention(RUNTIME)
+    @ConstraintAppender.Use(NullValue.Appender.class)
+    public @interface List {
+        /**
+         * Contained {@link NullValue} constraints.
+         * 
+         * @return NullValue[]
+         */
+        NullValue[] value();
+    }
+
+    /**
+     * Null value handling rules.
+     */
+    public enum Rule {
+        REQUIRE, ALLOW, DENY;
+    }
+
+    /**
+     * {@link ConstraintValidator} implementation for {@link NullValue} constraints.
+     */
+    public static class Validator implements ConstraintValidator<NullValue, Object> {
+        private Rule rule;
+
+        /**
+         * {@inheritDoc}
+         */
+        public void initialize(NullValue constraintAnnotation) {
+            this.rule = constraintAnnotation.value();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean isValid(Object value, ConstraintValidatorContext context) {
+            if (rule == Rule.REQUIRE) {
+                return value == null;
+            }
+            if (rule == Rule.DENY) {
+                return value != null;
+            }
+            return true;
+        }
+
+    }
+
+    /**
+     * {@link ConstraintAppender} implementation.
+     */
+    public static class Appender extends AbstractGenericMultivaluedConstraintAppender<NullValue, NullValue.List> {
+
+        /**
+         * Create a new Appender instance.
+         */
+        public Appender() {
+            super(NullValue.class, NullValue.List.class);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected boolean appendTyped(NullValue constraint, Collection<Annotation> collection) {
+            NullValue[] found = null;
+            for (Iterator<Annotation> iter = collection.iterator(); iter.hasNext();) {
+                Annotation a = iter.next();
+                if (a.annotationType().equals(NullValue.List.class)) {
+                    found = ((NullValue.List) a).value();
+                } else if (a.annotationType().equals(NullValue.class)) {
+                    found = new NullValue[] { (NullValue) a };
+                } else {
+                    continue;
+                }
+                iter.remove();
+            }
+            if (found == null || found.length == 0) {
+                return collection.add(constraint);
+            }
+            // otherwise, we first calculate original rules by group:
+            Map<Class<?>, Rule> rulesByGroup = new HashMap<Class<?>, NullValue.Rule>();
+            for (NullValue a : found) {
+                addRulesByGroup(rulesByGroup, a);
+            }
+            // then, replace the rule for this constraint's groups:
+            addRulesByGroup(rulesByGroup, constraint);
+
+            // now map group to constraint set, discarding constraints whose rule no longer matches the group's rule:
+            Map<Class<?>, Set<NullValue>> constraintsByGroup = new HashMap<Class<?>, Set<NullValue>>();
+            for (NullValue a : found) {
+                addConstraintByGroup(constraintsByGroup, a, rulesByGroup);
+            }
+            addConstraintByGroup(constraintsByGroup, constraint, rulesByGroup);
+
+            // check whether we have only a single constraint and return early:
+            if (constraintsByGroup.size() == 1 && constraintsByGroup.values().iterator().next().size() == 1) {
+                return collection.add(constraintsByGroup.values().iterator().next().iterator().next());
+            }
+
+            // set up a List to hold all our final constraints:
+            final ArrayList<NullValue> result = new ArrayList<NullValue>();
+
+            // fill list of results by processing per rule:
+            for (final Rule rule : Rule.values()) {
+                HashSet<Class<?>> groups = new HashSet<Class<?>>();
+                HashSet<NullValue> ruleConstraints = new HashSet<NullValue>();
+                for (Map.Entry<Class<?>, Rule> e : rulesByGroup.entrySet()) {
+                    if (e.getValue() == rule) {
+                        groups.add(e.getKey());
+                        ruleConstraints.addAll(constraintsByGroup.get(e.getKey()));
+                    }
+                }
+                if (ruleConstraints.isEmpty()) {
+                    continue;
+                }
+
+                HashSet<String> messages = new HashSet<String>();
+                HashSet<Class<? extends Payload>> payloads = new HashSet<Class<? extends Payload>>();
+                for (NullValue a : ruleConstraints) {
+                    messages.add(a.message());
+                    Collections.addAll(payloads, a.payload());
+                }
+
+                final Class<?>[] groupsArray = groups.toArray(new Class[groups.size()]);
+                final String message = StringUtils.join(messages, "; ");
+                @SuppressWarnings("unchecked")
+                final Class<? extends Payload>[] payload = payloads.toArray(new Class[payloads.size()]);
+
+                result.add(AnnotationFactory.INSTANCE.create(new StubConfigurer<NullValue>() {
+
+                    @Override
+                    protected void configure(NullValue stub) {
+                        when(stub.value()).thenReturn(rule).when(stub.groups()).thenReturn(groupsArray)
+                            .when(stub.message()).thenReturn(message).when(stub.payload()).thenReturn(payload);
+                    }
+                }));
+            }
+
+            if (result.size() == 0) {
+                // apparently all constraints were annihilated by contact with anti-particles:
+                return true;
+            }
+            Annotation toAdd;
+            if (result.size() == 1) {
+                toAdd = result.get(0);
+            } else {
+                toAdd = AnnotationFactory.INSTANCE.create(new StubConfigurer<NullValue.List>() {
+
+                    @Override
+                    protected void configure(List stub) {
+                        when(stub.value()).thenReturn(result.toArray(new NullValue[result.size()]));
+                    }
+                });
+            }
+            return collection.add(toAdd);
+        }
+
+        private static void addRulesByGroup(Map<Class<?>, Rule> toMap, NullValue fromConstraint) {
+            Groups groups = GROUPS_COMPUTER.computeGroups(fromConstraint.groups());
+            for (Group g : groups.getGroups()) {
+                Rule particle = fromConstraint.value();
+                if (particle == Rule.ALLOW && toMap.containsKey(g.getGroup())) {
+                    if (toMap.get(g.getGroup()) != particle) {
+                        //mutual annihilation: ALLOW + any other == none
+                        toMap.remove(g.getGroup());
+                    }
+                    continue;
+                }
+                toMap.put(g.getGroup(), particle);
+            }
+        }
+
+        private static void addConstraintByGroup(Map<Class<?>, Set<NullValue>> toMap, NullValue constraint,
+            Map<Class<?>, Rule> rulesByGroup) {
+            Groups groups = GROUPS_COMPUTER.computeGroups(constraint.groups());
+            for (Group g : groups.getGroups()) {
+                if (constraint.value() != rulesByGroup.get(g.getGroup())) {
+                    continue;
+                }
+                Set<NullValue> set = toMap.get(g.getGroup());
+                if (set == null) {
+                    set = new HashSet<NullValue>();
+                    toMap.put(g.getGroup(), set);
+                }
+                set.add(constraint);
+            }
+        }
+    }
+}

Propchange: incubator/bval/sandbox/lang3-work/bval-jsr303d/src/main/java/org/apache/bval/constraints/dynamic/NullValue.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/bval/sandbox/lang3-work/bval-jsr303d/src/test/java/org/apache/bval/constraints/dynamic/NullValueValidationTest.java
URL: http://svn.apache.org/viewvc/incubator/bval/sandbox/lang3-work/bval-jsr303d/src/test/java/org/apache/bval/constraints/dynamic/NullValueValidationTest.java?rev=1005258&view=auto
==============================================================================
--- incubator/bval/sandbox/lang3-work/bval-jsr303d/src/test/java/org/apache/bval/constraints/dynamic/NullValueValidationTest.java (added)
+++ incubator/bval/sandbox/lang3-work/bval-jsr303d/src/test/java/org/apache/bval/constraints/dynamic/NullValueValidationTest.java Wed Oct  6 21:18:01 2010
@@ -0,0 +1,284 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.bval.constraints.dynamic;
+
+import static org.junit.Assert.*;
+import static org.apache.bval.constraints.dynamic.NullValue.Rule.*;
+
+import java.util.Locale;
+
+import javax.validation.Valid;
+import javax.validation.Validation;
+import javax.validation.Validator;
+
+import org.apache.bval.jsr303.ApacheValidationProvider;
+import org.apache.bval.jsr303.ApacheValidatorConfiguration;
+import org.apache.bval.jsr303.DefaultMessageInterpolator;
+import org.apache.bval.jsr303.dynamic.DynamicValidatorContext;
+import org.apache.bval.jsr303.dynamic.DynamicValidatorFactory;
+import org.apache.commons.proxy2.stub.AnnotationFactory;
+import org.apache.commons.proxy2.stub.StubConfigurer;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test {@link NullValue} validation.
+ * 
+ * @version $Rev$ $Date$
+ */
+public class NullValueValidationTest {
+
+    public static class TestBean {
+        // default rule
+        @NullValue
+        private Object foo;
+
+        @NullValue(REQUIRE)
+        private Object bar;
+
+        @NullValue(ALLOW)
+        private Object baz;
+
+        @NullValue(DENY)
+        private Object boo;
+
+        // blank slate
+        private Object hoo;
+
+        @NullValue.List({ @NullValue(value = REQUIRE, groups = X.class), @NullValue(value = DENY, groups = Y.class) })
+        private Object groo;
+
+        public Object getFoo() {
+            return foo;
+        }
+
+        public void setFoo(Object foo) {
+            this.foo = foo;
+        }
+
+        public Object getBar() {
+            return bar;
+        }
+
+        public void setBar(Object bar) {
+            this.bar = bar;
+        }
+
+        public Object getBaz() {
+            return baz;
+        }
+
+        public void setBaz(Object baz) {
+            this.baz = baz;
+        }
+
+        public Object getBoo() {
+            return boo;
+        }
+
+        public void setBoo(Object boo) {
+            this.boo = boo;
+        }
+
+        public Object getHoo() {
+            return hoo;
+        }
+
+        public void setHoo(Object hoo) {
+            this.hoo = hoo;
+        }
+
+        public Object getGroo() {
+            return groo;
+        }
+
+        public void setGroo(Object groo) {
+            this.groo = groo;
+        }
+
+    }
+
+    public class TestBeanHolder {
+        @Valid
+        private TestBean testBean;
+
+        /**
+         * Create a new TestBeanHolder instance.
+         * 
+         * @param testBean
+         */
+        private TestBeanHolder(TestBean testBean) {
+            super();
+            this.testBean = testBean;
+        }
+
+        public TestBean getTestBean() {
+            return testBean;
+        }
+
+        public void setTestBean(TestBean testBean) {
+            this.testBean = testBean;
+        }
+    }
+
+    public interface W {
+    }
+
+    public interface X {
+    }
+
+    public interface Y {
+    }
+
+    public interface Z extends X, Y {
+    }
+
+    private Validator validator;
+    private DynamicValidatorContext validatorContext;
+    private TestBean testBean;
+
+    @Before
+    public void setup() {
+        DynamicValidatorFactory factory =
+            Validation
+                .byProvider(ApacheValidationProvider.class)
+                .configure()
+                .addProperty(ApacheValidatorConfiguration.Properties.VALIDATOR_FACTORY_CLASSNAME,
+                    DynamicValidatorFactory.class.getName()).buildValidatorFactory()
+                .unwrap(DynamicValidatorFactory.class);
+        ((DefaultMessageInterpolator) factory.getMessageInterpolator()).setLocale(Locale.ENGLISH);
+        validatorContext = factory.usingContext();
+        validator = validatorContext.getValidator();
+        testBean = new TestBean();
+    }
+
+    @Test
+    public void testBaseline() throws Exception {
+        assertEquals(0, validator.validateProperty(testBean, "foo").size());
+        assertEquals(0, validator.validateProperty(testBean, "bar").size());
+        assertEquals(0, validator.validateProperty(testBean, "baz").size());
+        assertEquals(0, validator.validateValue(TestBean.class, "baz", "whatever").size());
+        assertEquals(1, validator.validateProperty(testBean, "boo").size());
+        assertEquals(0, validator.validateProperty(testBean, "hoo").size());
+        assertEquals(0, validator.validateValue(TestBean.class, "hoo", "whatever").size());
+        assertEquals(0, validator.validateProperty(testBean, "groo").size());
+        assertEquals(0, validator.validateProperty(testBean, "groo", W.class).size());
+        assertEquals(0, validator.validateProperty(testBean, "groo", X.class).size());
+        assertEquals(1, validator.validateProperty(testBean, "groo", Y.class).size());
+        assertEquals(1, validator.validateProperty(testBean, "groo", Z.class).size());
+    }
+
+    /*
+     * // default rule
+     * 
+     * @NullValue private Object foo;
+     * 
+     * @NullValue(REQUIRE) private Object bar;
+     * 
+     * @NullValue(ALLOW) private Object baz;
+     * 
+     * @NullValue(DENY) private Object boo;
+     * 
+     * // blank slate private Object hoo;
+     * 
+     * @NullValue.List({ @NullValue(value=REQUIRE, groups=X.class), @NullValue(value=DENY, groups=Y.class)}) private
+     * Object groo;
+     */
+    @Test
+    public void testAppend() throws Exception {
+        validatorContext.constrain(TestBean.class, "foo", nullValue(ALLOW)); // was REQUIRE
+        validatorContext.constrain(TestBean.class, "bar", nullValue(DENY)); // was REQUIRE
+        validatorContext.constrain(TestBean.class, "baz", nullValue(REQUIRE)); // was ALLOW
+        validatorContext.constrain(TestBean.class, "boo", nullValue(REQUIRE)); // was DENY
+        validatorContext.constrain(TestBean.class, "hoo", nullValue(REQUIRE)); // was unspecified
+
+        assertEquals(0, validator.validateProperty(testBean, "foo").size());
+        assertEquals(0, validator.validateValue(TestBean.class, "foo", "whatever").size());
+        assertEquals(1, validator.validateProperty(testBean, "bar").size());
+        assertEquals(0, validator.validateProperty(testBean, "baz").size());
+        assertEquals(1, validator.validateValue(TestBean.class, "baz", "whatever").size());
+        assertEquals(0, validator.validateProperty(testBean, "boo").size());
+        assertEquals(1, validator.validateValue(TestBean.class, "boo", "whatever").size());
+        assertEquals(0, validator.validateProperty(testBean, "hoo").size());
+        assertEquals(1, validator.validateValue(TestBean.class, "hoo", "whatever").size());
+    }
+
+    @Test
+    public void testMultilevelAppend() throws Exception {
+        validatorContext.constrain(TestBeanHolder.class, "testBean.foo", nullValue(ALLOW)); // was REQUIRE
+        validatorContext.constrain(TestBeanHolder.class, "testBean.bar", nullValue(DENY)); // was REQUIRE
+        validatorContext.constrain(TestBeanHolder.class, "testBean.baz", nullValue(REQUIRE)); // was ALLOW
+        validatorContext.constrain(TestBeanHolder.class, "testBean.boo", nullValue(REQUIRE)); // was DENY
+        validatorContext.constrain(TestBeanHolder.class, "testBean.hoo", nullValue(REQUIRE)); // was unspecified
+
+        TestBeanHolder holder = new TestBeanHolder(testBean);
+        assertEquals(0, validator.validateProperty(holder, "testBean.foo").size());
+        assertEquals(0, validator.validateValue(TestBeanHolder.class, "testBean.foo", "whatever").size());
+        assertEquals(1, validator.validateProperty(holder, "testBean.bar").size());
+        assertEquals(0, validator.validateProperty(holder, "testBean.baz").size());
+        assertEquals(1, validator.validateValue(TestBeanHolder.class, "testBean.baz", "whatever").size());
+        assertEquals(0, validator.validateProperty(holder, "testBean.boo").size());
+        assertEquals(1, validator.validateValue(TestBeanHolder.class, "testBean.boo", "whatever").size());
+        assertEquals(0, validator.validateProperty(holder, "testBean.hoo").size());
+        assertEquals(1, validator.validateValue(TestBeanHolder.class, "testBean.hoo", "whatever").size());
+    }
+
+    @Test
+    public void testAppendWithGroups() throws Exception {
+        validatorContext.constrain(TestBean.class, "foo", nullValue(DENY, W.class)); // group W was unspecified but
+                                                                                     // default is REQUIRE
+        validatorContext.constrain(TestBean.class, "groo", nullValue(REQUIRE, W.class)); // group W was unspecified
+
+        assertEquals(0, validator.validateProperty(testBean, "foo").size());
+        assertEquals(1, validator.validateProperty(testBean, "foo", W.class).size());
+        assertEquals(0, validator.validateValue(TestBean.class, "foo", "whatever", W.class).size());
+        assertEquals(1, validator.validateValue(TestBean.class, "groo", "whatever", W.class).size());
+    }
+
+    @Test
+    public void testMultilevelAppendWithGroups() throws Exception {
+        validatorContext.constrain(TestBeanHolder.class, "testBean.foo", nullValue(DENY, W.class)); // group W was
+                                                                                                    // unspecified but
+                                                                                                    // default is
+                                                                                                    // REQUIRE
+        validatorContext.constrain(TestBeanHolder.class, "testBean.groo", nullValue(REQUIRE, W.class)); // group W was
+                                                                                                        // unspecified
+
+        TestBeanHolder holder = new TestBeanHolder(testBean);
+        assertEquals(0, validator.validateProperty(holder, "testBean.foo").size());
+        assertEquals(1, validator.validateProperty(holder, "testBean.foo", W.class).size());
+        assertEquals(0, validator.validateValue(TestBeanHolder.class, "testBean.foo", "whatever", W.class).size());
+        assertEquals(1, validator.validateValue(TestBeanHolder.class, "testBean.groo", "whatever", W.class).size());
+    }
+
+    @Test
+    public void testMutualAnnihilation() throws Exception {
+        validatorContext.constrain(TestBean.class, "foo", nullValue(ALLOW));
+        assertNull(validator.getConstraintsForClass(TestBean.class).getConstraintsForProperty("foo"));
+    }
+
+    private NullValue nullValue(final NullValue.Rule rule, final Class<?>... groups) {
+        return AnnotationFactory.INSTANCE.create(new StubConfigurer<NullValue>() {
+
+            @Override
+            protected void configure(NullValue stub) {
+                when(stub.value()).thenReturn(rule).when(stub.groups()).thenReturn(groups);
+            }
+        });
+    }
+
+}

Propchange: incubator/bval/sandbox/lang3-work/bval-jsr303d/src/test/java/org/apache/bval/constraints/dynamic/NullValueValidationTest.java
------------------------------------------------------------------------------
    svn:eol-style = native