Author: hlship
Date: Thu Dec 21 13:29:39 2006
New Revision: 489466
URL: http://svn.apache.org/viewvc?view=rev&rev=489466
Log:
Extend the FieldValidatorSource to provide a field validators based on a simple
text specification.
Added:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/FieldValidatorSource.java
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/FieldValidatorSourceImplTest.java
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java?view=diff&rev=489466&r1=489465&r2=489466
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java
Thu Dec 21 13:29:39 2006
@@ -14,9 +14,11 @@
package org.apache.tapestry.internal.services;
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
import static org.apache.tapestry.ioc.internal.util.Defense.cast;
import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -80,6 +82,34 @@
};
}
+ public FieldValidator createValidators(Field field, String specification)
+ {
+ List<ValidatorSpecification> specs = parse(specification);
+
+ List<FieldValidator> fieldValidators = newList();
+
+ for (ValidatorSpecification spec : specs)
+ {
+ fieldValidators.add(createValidator(field,
spec.getValidatorType(), spec
+ .getConstraintValue()));
+ }
+
+ if (fieldValidators.size() == 1)
+ return fieldValidators.get(0);
+
+ final FieldValidator[] array = fieldValidators.toArray(new
FieldValidator[0]);
+
+ return new FieldValidator()
+ {
+ @SuppressWarnings("unchecked")
+ public void check(Object value) throws ValidationException
+ {
+ for (FieldValidator fv : array)
+ fv.check(value);
+ }
+ };
+ }
+
@SuppressWarnings("unchecked")
private Object coerceConstraintValue(String constraintValue, Class
constraintType)
{
@@ -89,4 +119,177 @@
return _typeCoercer.coerce(constraintValue, constraintType);
}
+ /**
+ * A code defining what the parser is looking for.
+ */
+ enum State {
+
+ /** The start of a validator type. */
+ TYPE_START,
+ /** The end of a validator type. */
+ TYPE_END,
+ /** Equals sign after a validator type, or a comma. */
+ EQUALS_OR_COMMA,
+ /** The start of a constraint value. */
+ VALUE_START,
+ /** The end of the constraint value. */
+ VALUE_END,
+ /** The comma after a constraint value. */
+ COMMA
+ };
+
+ static List<ValidatorSpecification> parse(String specification)
+ {
+ List<ValidatorSpecification> result = newList();
+
+ char[] input = specification.toCharArray();
+
+ int cursor = 0;
+ int start = -1;
+
+ String type = null;
+ String value = null;
+ boolean skipWhitespace = true;
+ State state = State.TYPE_START;
+
+ while (cursor < input.length)
+ {
+ char ch = input[cursor];
+
+ if (skipWhitespace && Character.isWhitespace(ch))
+ {
+ cursor++;
+ continue;
+ }
+
+ skipWhitespace = false;
+
+ switch (state)
+ {
+
+ case TYPE_START:
+
+ if (Character.isLetter(ch))
+ {
+ start = cursor;
+ state = State.TYPE_END;
+ break;
+ }
+
+ parseError(cursor, specification);
+
+ case TYPE_END:
+
+ if (Character.isLetter(ch))
+ {
+ break;
+ }
+
+ type = specification.substring(start, cursor);
+
+ skipWhitespace = true;
+ state = State.EQUALS_OR_COMMA;
+ continue;
+
+ case EQUALS_OR_COMMA:
+
+ if (ch == '=')
+ {
+ skipWhitespace = true;
+ state = State.VALUE_START;
+ break;
+ }
+
+ if (ch == ',')
+ {
+ result.add(new ValidatorSpecification(type));
+ type = null;
+ state = State.COMMA;
+ continue;
+ }
+
+ parseError(cursor, specification);
+
+ case VALUE_START:
+
+ start = cursor;
+ state = State.VALUE_END;
+ break;
+
+ case VALUE_END:
+
+ // The value ends when we hit whitespace or a comma
+
+ if (Character.isWhitespace(ch) || ch == ',')
+ {
+ value = specification.substring(start, cursor);
+
+ result.add(new ValidatorSpecification(type, value));
+ type = null;
+ value = null;
+
+ skipWhitespace = true;
+ state = State.COMMA;
+ continue;
+ }
+
+ break;
+
+ case COMMA:
+
+ if (ch == ',')
+ {
+ skipWhitespace = true;
+ state = State.TYPE_START;
+ break;
+
+ }
+
+ parseError(cursor, specification);
+ } // case
+
+ cursor++;
+ } // while
+
+ // cursor is now one character past end of string.
+ // Cleanup whatever state we were in the middle of.
+
+ switch (state)
+ {
+ case TYPE_END:
+
+ type = specification.substring(start);
+
+ case EQUALS_OR_COMMA:
+
+ result.add(new ValidatorSpecification(type));
+ break;
+
+ // Case when the specification ends with an equals sign.
+
+ case VALUE_START:
+ result.add(new ValidatorSpecification(type, ""));
+ break;
+
+ case VALUE_END:
+ value = specification.substring(start);
+
+ result.add(new ValidatorSpecification(type, value));
+ break;
+
+ // For better or worse, ending the string with a comma is valid.
+
+ default:
+
+ }
+
+ return result;
+ }
+
+ private static void parseError(int cursor, String specification)
+ {
+ throw new
RuntimeException(ServicesMessages.validatorSpecificationParseError(
+ cursor,
+ specification));
+ }
}
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java?view=diff&rev=489466&r1=489465&r2=489466
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
Thu Dec 21 13:29:39 2006
@@ -278,4 +278,13 @@
return MESSAGES.format("unknown-validator-type", validatorType,
InternalUtils
.join(knownValidatorTypes));
}
+
+ static String validatorSpecificationParseError(int cursor, String
specification)
+ {
+ return MESSAGES.format(
+ "validator-specification-parse-error",
+ specification.charAt(cursor),
+ cursor + 1,
+ specification);
+ }
}
Added:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java?view=auto&rev=489466
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java
(added)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java
Thu Dec 21 13:29:39 2006
@@ -0,0 +1,56 @@
+package org.apache.tapestry.internal.services;
+
+/** Validator type and constraint values parsed from a validator
specification. */
+class ValidatorSpecification
+{
+ private final String _validatorType;
+
+ private final String _constraintValue;
+
+ public ValidatorSpecification(String validatorType)
+ {
+ this(validatorType, null);
+ }
+
+ public ValidatorSpecification(String validatorType, String constraintValue)
+ {
+ _validatorType = validatorType;
+ _constraintValue = constraintValue;
+ }
+
+ public String getConstraintValue()
+ {
+ return _constraintValue;
+ }
+
+ public String getValidatorType()
+ {
+ return _validatorType;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("ValidatorSpecification[%s %s]", _validatorType,
_constraintValue);
+ }
+
+ @Override
+ public boolean equals(Object other)
+ {
+ if (other == null || other.getClass() != getClass())
+ return false;
+
+ ValidatorSpecification ov = (ValidatorSpecification) other;
+
+ if (!_validatorType.equals(ov._validatorType))
+ return false;
+
+ if (_constraintValue == ov._constraintValue)
+ return true;
+
+ if (_constraintValue == null)
+ return false;
+
+ return _constraintValue.equals(ov._constraintValue);
+ }
+}
\ No newline at end of file
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/FieldValidatorSource.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/FieldValidatorSource.java?view=diff&rev=489466&r1=489465&r2=489466
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/FieldValidatorSource.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/FieldValidatorSource.java
Thu Dec 21 13:29:39 2006
@@ -37,4 +37,15 @@
* @return the field validator for the field
*/
FieldValidator createValidator(Field field, String validatorType, String
constraintValue);
+
+ /**
+ * Creates a set of validators. The specification is a string used to
identify and configure the
+ * individual validators. The specification is a comma-separated list of
terms. Each term is a
+ * validator type name and an optional constraint value (seperated by an
equals sign).
+ *
+ * @param field
+ * @param specification
+ * @return a composite field validator
+ */
+ FieldValidator createValidators(Field field, String specification);
}
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java?view=diff&rev=489466&r1=489465&r2=489466
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
Thu Dec 21 13:29:39 2006
@@ -28,6 +28,7 @@
import org.apache.commons.logging.Log;
import org.apache.tapestry.ComponentEventHandler;
import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.Validator;
import org.apache.tapestry.annotations.AfterRender;
import org.apache.tapestry.annotations.AfterRenderBody;
import org.apache.tapestry.annotations.AfterRenderTemplate;
@@ -64,6 +65,7 @@
import org.apache.tapestry.internal.services.DefaultInjectionProvider;
import org.apache.tapestry.internal.services.EnvironmentImpl;
import org.apache.tapestry.internal.services.EnvironmentalWorker;
+import org.apache.tapestry.internal.services.FieldValidatorSourceImpl;
import org.apache.tapestry.internal.services.HeartbeatImpl;
import org.apache.tapestry.internal.services.InfrastructureImpl;
import org.apache.tapestry.internal.services.InfrastructureManagerImpl;
@@ -117,6 +119,7 @@
import org.apache.tapestry.ioc.internal.util.InternalUtils;
import org.apache.tapestry.ioc.services.ChainBuilder;
import org.apache.tapestry.ioc.services.ClassFactory;
+import org.apache.tapestry.ioc.services.FieldValidatorSource;
import org.apache.tapestry.ioc.services.PipelineBuilder;
import org.apache.tapestry.ioc.services.PropertyShadowBuilder;
import org.apache.tapestry.ioc.services.StrategyBuilder;
@@ -411,9 +414,7 @@
*/
public static void contributeInfrastructure(
Configuration<InfrastructureContribution> configuration,
ServiceLocator locator,
- @InjectService("Request")
- Request request, @InjectService("Response")
- Response response, @InjectService("tapestry.ioc.TypeCoercer")
+ @InjectService("tapestry.ioc.TypeCoercer")
TypeCoercer typeCoercer)
{
add(
@@ -430,7 +431,8 @@
ClasspathAssetAliasManager.class,
Request.class,
Response.class,
- ValidationMessagesSource.class);
+ ValidationMessagesSource.class,
+ FieldValidatorSource.class);
configuration.add(new InfrastructureContribution("TypeCoercer",
typeCoercer));
}
@@ -855,5 +857,13 @@
MappedConfiguration<String, String> configuration)
{
configuration.add("tapestry/", "org/apache/tapestry/");
+ }
+
+ public static FieldValidatorSource buildFieldValidatorSource(
+ @Inject("infrastructure:ValidationMessagesSource")
+ ValidationMessagesSource messagesSource,
@Inject("infrastructure:TypeCoercer")
+ TypeCoercer typeCoercer, Map<String, Validator> configuration)
+ {
+ return new FieldValidatorSourceImpl(messagesSource, typeCoercer,
configuration);
}
}
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties?view=diff&rev=489466&r1=489465&r2=489466
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
Thu Dec 21 13:29:39 2006
@@ -64,4 +64,5 @@
asset-does-not-exist=Unable to locate asset '%s' (the file does not exist).
wrong-asset-digest=The asset digest in the request does not match the actual
digest for asset '%s'. This indicates that the content of the asset has changed
between requests.
component-not-assignable-to-field=Component %s is not assignable to field %s
(of type %s).
-unknown-validator-type=Unknown validator type '%s'. Configured validators are
%s.
\ No newline at end of file
+unknown-validator-type=Unknown validator type '%s'. Configured validators are
%s.
+validator-specification-parse-error=Unexpected character '%s' at position %d
of input string: %s
\ No newline at end of file
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/FieldValidatorSourceImplTest.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/FieldValidatorSourceImplTest.java?view=diff&rev=489466&r1=489465&r2=489466
==============================================================================
---
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/FieldValidatorSourceImplTest.java
(original)
+++
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/FieldValidatorSourceImplTest.java
Thu Dec 21 13:29:39 2006
@@ -17,6 +17,8 @@
import static java.util.Collections.singletonMap;
import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
+import java.util.Arrays;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -107,6 +109,83 @@
@SuppressWarnings("unchecked")
@Test
+ public void single_validator_via_specification() throws Exception
+ {
+ ValidationMessagesSource messagesSource =
newValidationMessagesSource();
+ Validator validator = newValidator();
+ TypeCoercer coercer = newTypeCoercer();
+ FieldComponent field = newFieldComponent();
+ Messages messages = newMock(Messages.class);
+ Object inputValue = new Object();
+ ComponentResources resources = newComponentResources();
+
+ Map<String, Validator> map = singletonMap("required", validator);
+
+ train_getConstraintType(validator, null);
+
+ train_getComponentResources(field, resources);
+ train_getLocale(resources, Locale.FRENCH);
+
+ train_getValidationMessages(messagesSource, Locale.FRENCH, messages);
+
+ validator.check(field, null, messages, inputValue);
+
+ replay();
+
+ FieldValidatorSource source = new
FieldValidatorSourceImpl(messagesSource, coercer, map);
+
+ FieldValidator fieldValidator = source.createValidators(field,
"required");
+
+ fieldValidator.check(inputValue);
+
+ verify();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void multiple_validators_via_specification() throws Exception
+ {
+ ValidationMessagesSource messagesSource =
newValidationMessagesSource();
+ Validator required = newValidator();
+ Validator minLength = newValidator();
+ TypeCoercer coercer = newTypeCoercer();
+ FieldComponent field = newFieldComponent();
+ Messages messages = newMock(Messages.class);
+ Object inputValue = new Object();
+ ComponentResources resources = newComponentResources();
+ Integer fifteen = 15;
+
+ Map<String, Validator> map = newMap();
+
+ map.put("required", required);
+ map.put("minLength", minLength);
+
+ train_getConstraintType(required, null);
+ train_getConstraintType(minLength, Integer.class);
+
+ train_getComponentResources(field, resources);
+ train_getLocale(resources, Locale.FRENCH);
+
+ train_getValidationMessages(messagesSource, Locale.FRENCH, messages);
+
+ train_coerce(coercer, "15", Integer.class, fifteen);
+
+ required.check(field, null, messages, inputValue);
+ minLength.check(field, fifteen, messages, inputValue);
+
+ replay();
+
+ FieldValidatorSource source = new
FieldValidatorSourceImpl(messagesSource, coercer, map);
+
+ FieldValidator fieldValidator = source.createValidators(field,
"required,minLength=15");
+
+ fieldValidator.check(inputValue);
+
+ verify();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
public void validator_with_constraint() throws Exception
{
ValidationMessagesSource messagesSource =
newValidationMessagesSource();
@@ -145,5 +224,90 @@
private FieldComponent newFieldComponent()
{
return newMock(FieldComponent.class);
+ }
+
+ private void test(String specification, ValidatorSpecification... expected)
+ {
+ List<ValidatorSpecification> specs =
FieldValidatorSourceImpl.parse(specification);
+
+ assertEquals(specs, Arrays.asList(expected));
+ }
+
+ @Test
+ public void parse_simple_type_list()
+ {
+ test(
+ "required,email",
+ new ValidatorSpecification("required", null),
+ new ValidatorSpecification("email", null));
+ }
+
+ @Test
+ public void parse_single_type()
+ {
+ test("required", new ValidatorSpecification("required", null));
+ }
+
+ @Test
+ public void ignore_whitespace_around_type_name()
+ {
+ 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"));
+ }
+
+ @Test
+ public void whitespace_ignored_around_value()
+ {
+ test("minLength= 5 , sameAs = otherComponentId ", new
ValidatorSpecification("minLength",
+ "5"), new ValidatorSpecification("sameAs",
"otherComponentId"));
+ }
+
+ @Test
+ public void dangling_equals_sign_is_empty_string_value()
+ {
+ test("minLength= ", new ValidatorSpecification("minLength", ""));
+ }
+
+ @Test
+ public void unexpected_character_not_a_comma()
+ {
+ try
+ {
+ test("required.email");
+ unreachable();
+ }
+ catch (RuntimeException ex)
+ {
+ assertEquals(
+ ex.getMessage(),
+ "Unexpected character '.' at position 9 of input string:
required.email");
+ }
+ }
+
+ @Test
+ public void unexpected_character_after_constraint_value()
+ {
+ try
+ {
+ test("minLength=3 . email");
+ unreachable();
+ }
+ catch (RuntimeException ex)
+ {
+ assertEquals(
+ ex.getMessage(),
+ "Unexpected character '.' at position 13 of input string:
minLength=3 . email");
+ }
}
}