Author: hlship Date: Wed Feb 24 00:52:43 2010 New Revision: 915625 URL: http://svn.apache.org/viewvc?rev=915625&view=rev Log: TAP5-1028: Validator Macros: Combine multiple common validators into a single term
Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ValidatorMacroImpl.java (with props) tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/validator/ValidatorMacro.java (with props) Modified: tapestry/tapestry5/trunk/tapestry-annotations/src/main/java/org/apache/tapestry5/beaneditor/Validate.java tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImpl.java tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImplTest.java Modified: tapestry/tapestry5/trunk/tapestry-annotations/src/main/java/org/apache/tapestry5/beaneditor/Validate.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-annotations/src/main/java/org/apache/tapestry5/beaneditor/Validate.java?rev=915625&r1=915624&r2=915625&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-annotations/src/main/java/org/apache/tapestry5/beaneditor/Validate.java (original) +++ tapestry/tapestry5/trunk/tapestry-annotations/src/main/java/org/apache/tapestry5/beaneditor/Validate.java Wed Feb 24 00:52:43 2010 @@ -1,10 +1,10 @@ -// Copyright 2007, 2008, 2009 The Apache Software Foundation +// Copyright 2007, 2008, 2009, 2010 The Apache Software Foundation // // Licensed 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 +// 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, @@ -14,29 +14,34 @@ package org.apache.tapestry5.beaneditor; -import org.apache.tapestry5.ioc.annotations.UseWith; -import org.apache.tapestry5.ioc.annotations.AnnotationUseContext; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.BEAN; +import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.COMPONENT; +import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.MIXIN; +import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.PAGE; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; -import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Target; -import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.*; +import org.apache.tapestry5.ioc.annotations.UseWith; /** * Used to attach validation constraints directly to a property (either the getter or the setter method). The annotation * value is a comma separated list of <em>validation constraints</em>, each one identifying a validator type (such as * "required", "minlength") and optionally, a constraint value. Most validators need a constraint value, which is - * separated from the type by an equals size (i.e., "maxlength=30"). + * separated from the type by an equals size (i.e., "maxlength=30"). In addition, the value may include + * validator macros. * <p/> * May be placed on any getter or setter method, or on the matching field. */ -...@target({ElementType.FIELD, ElementType.METHOD}) +...@target( +{ ElementType.FIELD, ElementType.METHOD }) @Retention(RUNTIME) @Documented -...@usewith({BEAN,COMPONENT,MIXIN,PAGE}) +...@usewith( +{ BEAN, COMPONENT, MIXIN, PAGE }) public @interface Validate { String value(); Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImpl.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImpl.java?rev=915625&r1=915624&r2=915625&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImpl.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImpl.java Wed Feb 24 00:52:43 2010 @@ -1,10 +1,10 @@ -// Copyright 2006, 2007, 2008 The Apache Software Foundation +// Copyright 2006, 2007, 2008, 2010 The Apache Software Foundation // // Licensed 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 +// 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, @@ -14,26 +14,30 @@ package org.apache.tapestry5.internal.services; +import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList; +import static org.apache.tapestry5.ioc.internal.util.Defense.cast; +import static org.apache.tapestry5.ioc.internal.util.Defense.notBlank; + +import java.util.List; +import java.util.Locale; +import java.util.Map; + import org.apache.tapestry5.ComponentResources; import org.apache.tapestry5.Field; import org.apache.tapestry5.FieldValidator; import org.apache.tapestry5.Validator; import org.apache.tapestry5.ioc.MessageFormatter; import org.apache.tapestry5.ioc.Messages; -import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList; -import static org.apache.tapestry5.ioc.internal.util.Defense.cast; -import static org.apache.tapestry5.ioc.internal.util.Defense.notBlank; +import org.apache.tapestry5.ioc.internal.util.CollectionFactory; import org.apache.tapestry5.ioc.internal.util.InternalUtils; import org.apache.tapestry5.ioc.services.TypeCoercer; import org.apache.tapestry5.runtime.Component; import org.apache.tapestry5.services.FieldValidatorSource; import org.apache.tapestry5.services.FormSupport; import org.apache.tapestry5.services.ValidationMessagesSource; +import org.apache.tapestry5.validator.ValidatorMacro; -import java.util.List; -import java.util.Locale; -import java.util.Map; - +...@suppresswarnings("unchecked") public class FieldValidatorSourceImpl implements FieldValidatorSource { private final ValidationMessagesSource messagesSource; @@ -44,13 +48,16 @@ private final FormSupport formSupport; + private final ValidatorMacro validatorMacro; + public FieldValidatorSourceImpl(ValidationMessagesSource messagesSource, TypeCoercer typeCoercer, - FormSupport formSupport, Map<String, Validator> validators) + FormSupport formSupport, Map<String, Validator> validators, ValidatorMacro validatorMacro) { this.messagesSource = messagesSource; this.typeCoercer = typeCoercer; this.formSupport = formSupport; this.validators = validators; + this.validatorMacro = validatorMacro; } public FieldValidator createValidator(Field field, String validatorType, String constraintValue) @@ -71,15 +78,15 @@ } public FieldValidator createValidator(Field field, String validatorType, String constraintValue, String overrideId, - Messages overrideMessages, Locale locale) + Messages overrideMessages, Locale locale) { notBlank(validatorType, "validatorType"); Validator validator = validators.get(validatorType); if (validator == null) - throw new IllegalArgumentException( - ServicesMessages.unknownValidatorType(validatorType, InternalUtils.sortedKeys(validators))); + throw new IllegalArgumentException(ServicesMessages.unknownValidatorType(validatorType, InternalUtils + .sortedKeys(validators))); // I just have this thing about always treating parameters as finals, so // we introduce a second variable to treat a mutable. @@ -87,66 +94,67 @@ String formValidationid = formSupport.getFormValidationId(); Object coercedConstraintValue = computeConstraintValue(validatorType, validator, constraintValue, - formValidationid, - overrideId, - overrideMessages); + formValidationid, overrideId, overrideMessages); MessageFormatter formatter = findMessageFormatter(formValidationid, overrideId, overrideMessages, locale, - validatorType, - validator); + validatorType, validator); return new FieldValidatorImpl(field, coercedConstraintValue, formatter, validator, formSupport); } private Object computeConstraintValue(String validatorType, Validator validator, String constraintValue, - String formId, String overrideId, - Messages overrideMessages) + String formId, String overrideId, Messages overrideMessages) { Class constraintType = validator.getConstraintType(); String constraintText = findConstraintValue(validatorType, constraintType, constraintValue, formId, overrideId, - overrideMessages); + overrideMessages); - if (constraintText == null) return null; + if (constraintText == null) + return null; return typeCoercer.coerce(constraintText, constraintType); } private String findConstraintValue(String validatorType, Class constraintType, String constraintValue, - String formValidationId, String overrideId, - Messages overrideMessages) + String formValidationId, String overrideId, Messages overrideMessages) { - if (constraintValue != null) return constraintValue; + if (constraintValue != null) + return constraintValue; - if (constraintType == null) return null; + if (constraintType == null) + return null; // If no constraint was provided, check to see if it is available via a localized message // key. This is really handy for complex validations such as patterns. String perFormKey = formValidationId + "-" + overrideId + "-" + validatorType; - if (overrideMessages.contains(perFormKey)) return overrideMessages.get(perFormKey); + if (overrideMessages.contains(perFormKey)) + return overrideMessages.get(perFormKey); String generalKey = overrideId + "-" + validatorType; - if (overrideMessages.contains(generalKey)) return overrideMessages.get(generalKey); + if (overrideMessages.contains(generalKey)) + return overrideMessages.get(generalKey); - throw new IllegalArgumentException( - ServicesMessages.missingValidatorConstraint(validatorType, constraintType, perFormKey, generalKey)); + throw new IllegalArgumentException(ServicesMessages.missingValidatorConstraint(validatorType, constraintType, + perFormKey, generalKey)); } private MessageFormatter findMessageFormatter(String formId, String overrideId, Messages overrideMessages, - Locale locale, - String validatorType, Validator validator) + Locale locale, String validatorType, Validator validator) { String overrideKey = formId + "-" + overrideId + "-" + validatorType + "-message"; - if (overrideMessages.contains(overrideKey)) return overrideMessages.getFormatter(overrideKey); + if (overrideMessages.contains(overrideKey)) + return overrideMessages.getFormatter(overrideKey); overrideKey = overrideId + "-" + validatorType + "-message"; - if (overrideMessages.contains(overrideKey)) return overrideMessages.getFormatter(overrideKey); + if (overrideMessages.contains(overrideKey)) + return overrideMessages.getFormatter(overrideKey); Messages messages = messagesSource.getValidationMessages(locale); @@ -157,21 +165,70 @@ public FieldValidator createValidators(Field field, String specification) { - List<ValidatorSpecification> specs = parse(specification); + List<ValidatorSpecification> specs = toValidatorSpecifications(specification); - List<FieldValidator> fieldValidators = newList(); + List<FieldValidator> fieldValidators = CollectionFactory.newList(); for (ValidatorSpecification spec : specs) { - fieldValidators.add(createValidator(field, spec.getValidatorType(), spec - .getConstraintValue())); + fieldValidators.add(createValidator(field, spec.getValidatorType(), spec.getConstraintValue())); } - if (fieldValidators.size() == 1) return fieldValidators.get(0); + if (fieldValidators.size() == 1) + return fieldValidators.get(0); return new CompositeFieldValidator(fieldValidators); } + List<ValidatorSpecification> toValidatorSpecifications(String specification) + { + return expandMacros(parse(specification)); + } + + private List<ValidatorSpecification> expandMacros(List<ValidatorSpecification> specs) + { + Map<String, Boolean> expandedMacros = CollectionFactory.newCaseInsensitiveMap(); + List<ValidatorSpecification> queue = CollectionFactory.newList(specs); + List<ValidatorSpecification> result = CollectionFactory.newList(); + + while (!queue.isEmpty()) + { + ValidatorSpecification head = queue.remove(0); + + String validatorType = head.getValidatorType(); + + String expanded = validatorMacro.valueForMacro(validatorType); + if (expanded != null) + { + if (head.getConstraintValue() != null) + throw new RuntimeException(String.format( + "'%s' is a validator macro, not a validator, and can not have a constraint value.", + validatorType)); + + if (expandedMacros.containsKey(validatorType)) + throw new RuntimeException(String.format("Validator macro '%s' appears more than once.", + validatorType)); + + expandedMacros.put(validatorType, true); + + List<ValidatorSpecification> parsed = parse(expanded); + + // Add the new validator specifications to the front of the queue, replacing the validator macro + + for (int i = 0; i < parsed.size(); i++) + { + queue.add(i, parsed.get(i)); + } + } + else + { + result.add(head); + } + } + + return result; + } + /** * A code defining what the parser is looking for. */ @@ -328,7 +385,7 @@ result.add(new ValidatorSpecification(type)); break; - // Case when the specification ends with an equals sign. + // Case when the specification ends with an equals sign. case VALUE_START: result.add(new ValidatorSpecification(type, "")); @@ -339,7 +396,7 @@ result.add(new ValidatorSpecification(type, specification.substring(start))); break; - // For better or worse, ending the string with a comma is valid. + // For better or worse, ending the string with a comma is valid. default: } Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ValidatorMacroImpl.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ValidatorMacroImpl.java?rev=915625&view=auto ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ValidatorMacroImpl.java (added) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ValidatorMacroImpl.java Wed Feb 24 00:52:43 2010 @@ -0,0 +1,35 @@ +// Copyright 2010 The Apache Software Foundation +// +// Licensed 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.tapestry5.internal.validator; + +import java.util.Map; + +import org.apache.tapestry5.validator.ValidatorMacro; + +public class ValidatorMacroImpl implements ValidatorMacro +{ + private final Map<String, String> configuration; + + public ValidatorMacroImpl(Map<String, String> configuration) + { + this.configuration = configuration; + } + + public String valueForMacro(String validatorMacro) + { + return configuration.get(validatorMacro); + } + +} Propchange: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ValidatorMacroImpl.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java?rev=915625&r1=915624&r2=915625&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java Wed Feb 24 00:52:43 2010 @@ -16,7 +16,6 @@ import java.io.IOException; import java.lang.annotation.Annotation; -import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URL; @@ -81,6 +80,7 @@ import org.apache.tapestry5.internal.util.PrimaryKeyEncoder2ValueEncoder; import org.apache.tapestry5.internal.util.RenderableAsBlock; import org.apache.tapestry5.internal.util.StringRenderable; +import org.apache.tapestry5.internal.validator.ValidatorMacroImpl; import org.apache.tapestry5.ioc.*; import org.apache.tapestry5.ioc.annotations.*; import org.apache.tapestry5.ioc.internal.util.CollectionFactory; @@ -104,6 +104,7 @@ import org.apache.tapestry5.validator.MinLength; import org.apache.tapestry5.validator.Regexp; import org.apache.tapestry5.validator.Required; +import org.apache.tapestry5.validator.ValidatorMacro; import org.slf4j.Logger; /** @@ -349,6 +350,7 @@ binder.bind(Dispatcher.class, AssetProtectionDispatcher.class).withId("AssetProtectionDispatcher"); binder.bind(AssetPathAuthorizer.class, WhitelistAuthorizer.class).withId("WhitelistAuthorizer"); binder.bind(AssetPathAuthorizer.class, RegexAuthorizer.class).withId("RegexAuthorizer"); + binder.bind(ValidatorMacro.class, ValidatorMacroImpl.class); } // ======================================================================== Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/validator/ValidatorMacro.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/validator/ValidatorMacro.java?rev=915625&view=auto ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/validator/ValidatorMacro.java (added) +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/validator/ValidatorMacro.java Wed Feb 24 00:52:43 2010 @@ -0,0 +1,35 @@ +// Copyright 2010 The Apache Software Foundation +// +// Licensed 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.tapestry5.validator; + +import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration; + +/** + * Allows support for "validator macros", a simple-minded way of combining several related valiations together under + * a single name. The service's configuration maps string keys (macro names) to string values (validation constraints). + * + * @since 5.2.0 + */ +...@usesmappedconfiguration(String.class) +public interface ValidatorMacro +{ + /** + * Given a <em>potential</em> validator macro (a simple string name), returns the value for that macro, a + * comma-separated list of validation constraints. + * + * @return constraints, or null if no such validator macro + */ + String valueForMacro(String validatorMacro); +} Propchange: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/validator/ValidatorMacro.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImplTest.java URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImplTest.java?rev=915625&r1=915624&r2=915625&view=diff ============================================================================== --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImplTest.java (original) +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImplTest.java Wed Feb 24 00:52:43 2010 @@ -1,10 +1,10 @@ -// Copyright 2006, 2007, 2008 The Apache Software Foundation +// Copyright 2006, 2007, 2008, 2010 The Apache Software Foundation // // Licensed 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 +// 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, @@ -22,11 +22,15 @@ import org.apache.tapestry5.ioc.MessageFormatter; import org.apache.tapestry5.ioc.Messages; import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newMap; + +import org.apache.tapestry5.ioc.internal.util.CollectionFactory; import org.apache.tapestry5.ioc.services.TypeCoercer; import org.apache.tapestry5.runtime.Component; import org.apache.tapestry5.services.FieldValidatorSource; import org.apache.tapestry5.services.FormSupport; import org.apache.tapestry5.services.ValidationMessagesSource; +import org.apache.tapestry5.validator.ValidatorMacro; +import org.easymock.EasyMock; import org.testng.annotations.Test; import java.util.Arrays; @@ -63,7 +67,7 @@ replay(); - FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, null, map); + FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, null, map, null); try { @@ -119,7 +123,7 @@ replay(); - FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map); + FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map, null); FieldValidator fieldValidator = source.createValidator(field, "required", null); @@ -164,7 +168,7 @@ replay(); - FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map); + FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map, null); FieldValidator fieldValidator = source.createValidator(field, "required", null); @@ -207,7 +211,7 @@ replay(); - FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map); + FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map, null); FieldValidator fieldValidator = source.createValidator(field, "required", null); @@ -231,7 +235,6 @@ Messages containerMessages = mockMessages(); FormSupport fs = mockFormSupport(); - Map<String, Validator> map = singletonMap("minlength", validator); train_getConstraintType(validator, Integer.class); @@ -262,9 +265,12 @@ train_getValueType(validator, Object.class); validator.validate(field, 5, formatter, inputValue); + ValidatorMacro macro = mockValidatorMacro(); + train_alwaysNull(macro); + replay(); - FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map); + FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map, macro); FieldValidator fieldValidator = source.createValidators(field, "minlength"); @@ -288,7 +294,6 @@ Messages containerMessages = mockMessages(); FormSupport fs = mockFormSupport(); - Map<String, Validator> map = singletonMap("minlength", validator); train_getConstraintType(validator, Integer.class); @@ -318,9 +323,12 @@ train_getValueType(validator, Object.class); validator.validate(field, 5, formatter, inputValue); + ValidatorMacro macro = mockValidatorMacro(); + train_alwaysNull(macro); + replay(); - FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map); + FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map, macro); FieldValidator fieldValidator = source.createValidators(field, "minlength"); @@ -329,7 +337,6 @@ verify(); } - @SuppressWarnings("unchecked") @Test public void missing_field_validator_constraint() throws Exception @@ -356,9 +363,12 @@ train_contains(containerMessages, "myform-fred-minlength", false); train_contains(containerMessages, "fred-minlength", false); + ValidatorMacro macro = mockValidatorMacro(); + train_alwaysNull(macro); + replay(); - FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map); + FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map, macro); try { @@ -367,8 +377,9 @@ } catch (IllegalArgumentException ex) { - assertEquals(ex.getMessage(), - "Validator 'minlength' requires a validation constraint (of type java.lang.Integer) but none was provided. The constraint may be provided inside the @Validator annotaton on the property, or in the associated component message catalog as key 'myform-fred-minlength' or key 'fred-minlength'. "); + assertEquals( + ex.getMessage(), + "Validator 'minlength' requires a validation constraint (of type java.lang.Integer) but none was provided. The constraint may be provided inside the @Validator annotaton on the property, or in the associated component message catalog as key 'myform-fred-minlength' or key 'fred-minlength'. "); } verify(); @@ -413,9 +424,12 @@ train_getValueType(validator, Object.class); validator.validate(field, null, formatter, inputValue); + ValidatorMacro macro = mockValidatorMacro(); + train_alwaysNull(macro); + replay(); - FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map); + FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map, macro); FieldValidator fieldValidator = source.createValidators(field, "required"); @@ -424,6 +438,89 @@ verify(); } + private void train_alwaysNull(ValidatorMacro macro) + { + expect(macro.valueForMacro(EasyMock.isA(String.class))).andReturn(null).anyTimes(); + } + + private ValidatorMacro mockValidatorMacro() + { + return newMock(ValidatorMacro.class); + } + + @SuppressWarnings("unchecked") + @Test + public void simple_macro_expansion() throws Exception + { + + ValidatorMacro macro = mockValidatorMacro(); + expect(macro.valueForMacro("combo")).andReturn("required,minlength=5"); + expect(macro.valueForMacro("required")).andReturn(null); + expect(macro.valueForMacro("minlength")).andReturn(null); + + replay(); + + FieldValidatorSourceImpl source = new FieldValidatorSourceImpl(null, null, null, null, macro); + + List<ValidatorSpecification> specs = source.toValidatorSpecifications("combo"); + + assertListsEquals(specs, new ValidatorSpecification("required"), new ValidatorSpecification("minlength", "5")); + + verify(); + } + + @SuppressWarnings("unchecked") + @Test + public void macros_can_not_have_constraints() throws Exception + { + + ValidatorMacro macro = mockValidatorMacro(); + expect(macro.valueForMacro("combo")).andReturn("required,minlength=5"); + + replay(); + + FieldValidatorSourceImpl source = new FieldValidatorSourceImpl(null, null, null, null, macro); + + try + { + source.toValidatorSpecifications("combo=3"); + unreachable(); + } + catch (RuntimeException ex) + { + assertEquals(ex.getMessage(), + "'combo' is a validator macro, not a validator, and can not have a constraint value."); + } + + verify(); + } + + @Test + public void recursive_macros_are_caught() + { + + ValidatorMacro macro = mockValidatorMacro(); + expect(macro.valueForMacro("combo")).andReturn("required,combo"); + expect(macro.valueForMacro("required")).andReturn(null); + expect(macro.valueForMacro("combo")).andReturn("required,combo"); + + replay(); + + FieldValidatorSourceImpl source = new FieldValidatorSourceImpl(null, null, null, null, macro); + + try + { + source.toValidatorSpecifications("combo"); + unreachable(); + } + catch (RuntimeException ex) + { + assertEquals(ex.getMessage(), "Validator macro 'combo' appears more than once."); + } + + verify(); + } + @SuppressWarnings("unchecked") @Test public void multiple_validators_via_specification() throws Exception @@ -481,9 +578,12 @@ train_getValueType(minLength, String.class); minLength.validate(field, fifteen, minLengthFormatter, inputValue); + ValidatorMacro macro = mockValidatorMacro(); + train_alwaysNull(macro); + replay(); - FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map); + FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map, macro); FieldValidator fieldValidator = source.createValidators(field, "required,minLength=15"); @@ -535,7 +635,7 @@ replay(); - FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map); + FieldValidatorSource source = new FieldValidatorSourceImpl(messagesSource, coercer, fs, map, null); FieldValidator fieldValidator = source.createValidator(field, "minLength", "5"); @@ -571,22 +671,22 @@ @Test public void ignore_whitespace_around_type_name() { - test(" required , email ", new ValidatorSpecification("required", null), - new ValidatorSpecification("email", null)); + test(" required , email ", new ValidatorSpecification("required", null), new ValidatorSpecification( + "email", null)); } @Test public void parse_simple_type_with_value() { test("minLength=5,sameAs=otherComponentId", new ValidatorSpecification("minLength", "5"), - new ValidatorSpecification("sameAs", "otherComponentId")); + new ValidatorSpecification("sameAs", "otherComponentId")); } @Test public void whitespace_ignored_around_value() { test("minLength= 5 , sameAs = otherComponentId ", new ValidatorSpecification("minLength", "5"), - new ValidatorSpecification("sameAs", "otherComponentId")); + new ValidatorSpecification("sameAs", "otherComponentId")); } @Test @@ -620,7 +720,7 @@ catch (RuntimeException ex) { assertEquals(ex.getMessage(), - "Unexpected character '.' at position 13 of input string: minLength=3 . email"); + "Unexpected character '.' at position 13 of input string: minLength=3 . email"); } } }