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