From 303d518cbe03ee9ad0b1d9771043b33ee620930e Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Wed, 5 Apr 2006 09:42:00 +0000 Subject: [PATCH] Further constraints - MINMAX (NumericRangeConstraint) - LENGTH (StringLengthConstraint) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2622 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../messages/dictionary-messages.properties | 12 +- .../repo/dictionary/DictionaryDAOTest.java | 2 +- .../dictionary/M2ConstraintDefinition.java | 18 ++ .../constraint/AbstractConstraint.java | 19 ++- .../constraint/ConstraintsTest.java | 159 +++++++++++++----- .../constraint/NumericRangeConstraint.java | 131 +++++++++++++++ .../constraint/RegexConstraint.java | 29 +++- .../constraint/StringLengthConstraint.java | 127 ++++++++++++++ .../dictionary/dictionarydaotest_model.xml | 12 ++ 9 files changed, 456 insertions(+), 53 deletions(-) create mode 100644 source/java/org/alfresco/repo/dictionary/constraint/NumericRangeConstraint.java create mode 100644 source/java/org/alfresco/repo/dictionary/constraint/StringLengthConstraint.java diff --git a/config/alfresco/messages/dictionary-messages.properties b/config/alfresco/messages/dictionary-messages.properties index 65e94c9866..2de0873860 100644 --- a/config/alfresco/messages/dictionary-messages.properties +++ b/config/alfresco/messages/dictionary-messages.properties @@ -23,4 +23,14 @@ d_dictionary.property.err.cannot_relax_mandatory=Cannot relax mandatory attribut d_dictionary.property.err.cannot_relax_mandatory_enforcement=Cannot relax mandatory attribute enforcement of property ''{0} d_dictionary.constraint.regex.no_match=Value ''{0}'' does not match regular expression: {1} -d_dictionary.constraint.regex.match=Value ''{0}'' matches regular expression: {1} \ No newline at end of file +d_dictionary.constraint.regex.match=Value ''{0}'' matches regular expression: {1} + +d_dictionary.constraint.numeric_range.invalid_min_value=Invalid ''minValue'' property: {0} +d_dictionary.constraint.numeric_range.invalid_max_value=Invalid ''maxValue'' property: {0} +d_dictionary.constraint.numeric_range.non_numeric=Property value could not be converted to a double: {0} +d_dictionary.constraint.numeric_range.out_of_range=Numeric value ''{0}'' is not in range [{1}; {2}] + +d_dictionary.constraint.string_length.invalid_min_length=Invalid ''minLength'' property: {0} +d_dictionary.constraint.string_length.invalid_max_length=Invalid ''maxLength'' property: {0} +d_dictionary.constraint.string_length.non_string=Property value could not be converted to a String: {0} +d_dictionary.constraint.string_length.invalid_length=String length of ''{0}'' is not in range [{1}; {2}] diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java index 11136a30bf..913e4dc11a 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java @@ -133,7 +133,7 @@ public class DictionaryDAOTest extends TestCase PropertyDefinition propDef = service.getProperty(prop1QName); List constraints = propDef.getConstraints(); assertNotNull("Null constraints list", constraints); - assertEquals("Incorrect number of constraints", 1, constraints.size()); + assertEquals("Incorrect number of constraints", 2, constraints.size()); // check the individual constraints ConstraintDefinition constraintDef = constraints.get(0); diff --git a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java index 9797abb3e2..b87e845bea 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java @@ -18,7 +18,9 @@ package org.alfresco.repo.dictionary; import java.util.List; +import org.alfresco.repo.dictionary.constraint.NumericRangeConstraint; import org.alfresco.repo.dictionary.constraint.RegexConstraint; +import org.alfresco.repo.dictionary.constraint.StringLengthConstraint; import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; @@ -226,6 +228,22 @@ import org.springframework.beans.TypeMismatchException; { return new RegexConstraint(); } + }, + MINMAX + { + @Override + protected Constraint newInstance() + { + return new NumericRangeConstraint(); + } + }, + LENGTH + { + @Override + protected Constraint newInstance() + { + return new StringLengthConstraint(); + } }; /** diff --git a/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java index 71f46ccab6..39dc1077c2 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java @@ -21,6 +21,7 @@ import java.util.Collection; import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintException; import org.alfresco.service.cmr.dictionary.DictionaryException; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; /** * Base services for constraints. @@ -39,12 +40,17 @@ public abstract class AbstractConstraint implements Constraint @SuppressWarnings("unchecked") public final void evaluate(Object value) { + if (value == null) + { + // null values are never evaluated + return; + } try { - // check for collection - if (value instanceof Collection) + // ensure that we can handle collections + if (DefaultTypeConverter.INSTANCE.isMultiValued(value)) { - Collection collection = (Collection) value; + Collection collection = DefaultTypeConverter.INSTANCE.getCollection(Object.class, value); evaluateCollection(collection); } else @@ -75,13 +81,18 @@ public abstract class AbstractConstraint implements Constraint { for (Object value : collection) { + if (value == null) + { + // contract states that it will always pass + continue; + } evaluateSingleValue(value); } } /** * Support for evaluation of properties. The value passed in will never be a - * collection. + * Collection and will never be null. * * @throws ConstraintException throw this when the evaluation fails */ diff --git a/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java b/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java index 4966ea2fe2..d46d60f1e4 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java @@ -17,13 +17,16 @@ package org.alfresco.repo.dictionary.constraint; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import junit.framework.TestCase; import org.alfresco.i18n.I18NUtil; import org.alfresco.repo.dictionary.DictionaryDAOTest; +import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintException; +import org.alfresco.service.cmr.dictionary.DictionaryException; /** * This file must be saved using UTF-8. @@ -63,11 +66,113 @@ public class ConstraintsTest extends TestCase catch (ConstraintException e) { // expected + checkI18NofExceptionMessage(e); } // check that the two strings were properly dealt with assertEquals("String values not checked", 2, constraint.tested.size()); } + public void testNull() throws Exception + { + DummyConstraint constraint = new DummyConstraint(); + constraint.initialize(); + + // a null always passes + constraint.evaluate(null); + } + + private void checkI18NofExceptionMessage(Throwable e) + { + String msg = e.getMessage(); + assertFalse("I18N of constraint message failed", msg.startsWith("d_dictionary.constraint")); + } + + private void evaluate(Constraint constraint, Object value, boolean expectFailure) throws Exception + { + try + { + constraint.evaluate(value); + if (expectFailure) + { + // it should have failed + fail("Failure did not occur: \n" + + " constraint: " + constraint + "\n" + + " value: " + value); + } + } + catch (ConstraintException e) + { + // check if we expect an error + if (expectFailure) + { + // expected - check message I18N + checkI18NofExceptionMessage(e); + } + else + { + // didn't expect it + throw e; + } + } + } + + public void testStringLengthConstraint() throws Exception + { + StringLengthConstraint constraint = new StringLengthConstraint(); + try + { + constraint.setMinLength(-1); + } + catch (DictionaryException e) + { + // expected + checkI18NofExceptionMessage(e); + } + try + { + constraint.setMaxLength(-1); + } + catch (DictionaryException e) + { + // expected + checkI18NofExceptionMessage(e); + } + constraint.setMinLength(3); + constraint.setMaxLength(6); + + evaluate(constraint, "abc", false); + evaluate(constraint, "abcdef", false); + evaluate(constraint, Arrays.asList("abc", "abcdef"), false); + evaluate(constraint, "ab", true); + evaluate(constraint, "abcdefg", true); + evaluate(constraint, Arrays.asList("abc", "abcdefg"), true); + } + + public void testNumericRangeConstraint() throws Exception + { + NumericRangeConstraint constraint = new NumericRangeConstraint(); + constraint.initialize(); + + // check that Double.MIN_VALUE and Double.MAX_VALUE are allowed by default + constraint.evaluate(Double.MIN_VALUE); + constraint.evaluate(Double.MAX_VALUE); + + // check that Double.NaN is not allowed by default + evaluate(constraint, Double.NaN, true); + + // set some limits and check + constraint.setMinValue(-5.0D); + constraint.setMaxValue(+5.0D); + constraint.initialize(); + + evaluate(constraint, "-1.0", false); + evaluate(constraint, "+1.0", false); + evaluate(constraint, Arrays.asList(-1, 0, 1), false); + evaluate(constraint, "abc", true); + evaluate(constraint, 56.453E4, true); + evaluate(constraint, Arrays.asList(-1, 6), true); + } + public void testRegexConstraint() throws Exception { RegexConstraint constraint = new RegexConstraint(); @@ -76,38 +181,22 @@ public class ConstraintsTest extends TestCase constraint.initialize(); // do some successful stuff - constraint.evaluate("ABC"); - constraint.evaluate("DEF"); + evaluate(constraint, "ABC", false); + evaluate(constraint, "DEF", false); // now some failures - try - { - constraint.evaluate("abc"); - fail("Regular expression evaluation should have failed: abc"); - } - catch (ConstraintException e) - { - String msg = e.getMessage(); - assertFalse("I18N of constraint message failed", msg.startsWith("d_dictionary.constraint")); - } + evaluate(constraint, "abc", true); // now a case of passing in an object that could be a string - constraint.evaluate(DummyEnum.ABC); - constraint.evaluate(DummyEnum.DEF); - try - { - constraint.evaluate(DummyEnum.abc); - fail("Regular expression evaluation should have failed for enum: " + DummyEnum.abc); - } - catch (ConstraintException e) - { - } + evaluate(constraint, DummyEnum.ABC, false); + evaluate(constraint, DummyEnum.DEF, false); + evaluate(constraint, DummyEnum.abc, true); // now switch the requiresMatch around constraint.setRequiresMatch(false); constraint.initialize(); - constraint.evaluate(DummyEnum.abc); + evaluate(constraint, DummyEnum.abc, false); } public void testRegexConstraintFilename() throws Exception @@ -125,30 +214,12 @@ public class ConstraintsTest extends TestCase for (int i = 0; i < invalidChars.length(); i++) { String invalidStr = invalidChars.substring(i, i+1); - try - { - constraint.evaluate(invalidStr); - fail("Regex constraint failed to detect illegal characters: \n" + - " constraint: " + constraint + "\n" + - " invalid string: " + invalidStr); - } - catch (ConstraintException e) - { - // expected - } + evaluate(constraint, invalidStr, true); } // check a bogus filename - try - { - constraint.evaluate("Bogus<>.txt"); - fail("Failed to detect invalid filename"); - } - catch (ConstraintException e) - { - // expected - } + evaluate(constraint, "Bogus<>.txt", true); // ... and a valid one - constraint.evaluate("Company Home"); + evaluate(constraint, "Company Home", false); } private enum DummyEnum diff --git a/source/java/org/alfresco/repo/dictionary/constraint/NumericRangeConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/NumericRangeConstraint.java new file mode 100644 index 0000000000..26360d2aa5 --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/constraint/NumericRangeConstraint.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.dictionary.constraint; + +import org.alfresco.service.cmr.dictionary.ConstraintException; +import org.alfresco.service.cmr.dictionary.DictionaryException; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; + +/** + * Constraint implementation that ensures that the value is a numeric value bewteen a given + * minimum and maximum value. If a minimum or maximum value are not provided, then the JAVA + * Double's {@link Double#MIN_VALUE minimum value} or {@link Double#MAX_VALUE maximum value} + * are assumed. + * + * @see #setMinValue(double) + * @see #setMaxValue(double) + * @see java.lang.Double#parseDouble(java.lang.String) + * + * @author Derek Hulley + */ +public class NumericRangeConstraint extends AbstractConstraint +{ + private static final String ERR_INVALID_MIN_VALUE = "d_dictionary.constraint.numeric_range.invalid_min_value"; + private static final String ERR_INVALID_MAX_VALUE = "d_dictionary.constraint.numeric_range.invalid_max_value"; + private static final String ERR_NON_NUMERIC = "d_dictionary.constraint.numeric_range.non_numeric"; + private static final String ERR_OUT_OF_RANGE = "d_dictionary.constraint.numeric_range.out_of_range"; + + private double minValue = Double.MIN_VALUE; + private double maxValue = Double.MAX_VALUE; + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(80); + sb.append("NumericRangeConstraint") + .append("[ minValue=").append(minValue) + .append(", maxValue=").append(maxValue) + .append("]"); + return sb.toString(); + } + + /** + * @return Returns the minimum value allowed by the constraint + */ + public double getMinValue() + { + return minValue; + } + + /** + * Set the minimum value allowed, which can be any value between + * {@link Double#MIN_VALUE} and {@link Double#MAX_VALUE}. + * + * @param minValue the minimum value allowed by the constraint + */ + public void setMinValue(double minValue) + { + if (minValue > this.maxValue) + { + throw new DictionaryException(ERR_INVALID_MIN_VALUE, minValue); + } + this.minValue = minValue; + } + + /** + * @return Returns the minimum value allowed by the constraint + */ + public double getMaxValue() + { + return maxValue; + } + + /** + * Set the maximum value allowed, which can be any value between + * {@link Double#MIN_VALUE} and {@link Double#MAX_VALUE}. + * + * @param maxValue the minimum value allowed by the constraint + */ + public void setMaxValue(double maxValue) + { + if (maxValue < this.minValue) + { + throw new DictionaryException(ERR_INVALID_MAX_VALUE, minValue); + } + this.maxValue = maxValue; + } + + public void initialize() + { + } + + protected void evaluateSingleValue(Object value) + { + // ensure that the value can be converted to a double + double checkValue = Double.NaN; + try + { + checkValue = DefaultTypeConverter.INSTANCE.doubleValue(value); + } + catch (NumberFormatException e) + { + throw new ConstraintException(ERR_NON_NUMERIC, value); + } + + // Infinity and NaN cannot match + if (Double.isInfinite(checkValue) || Double.isNaN(checkValue)) + { + throw new ConstraintException(ERR_OUT_OF_RANGE, checkValue, minValue, maxValue); + } + + // Check that the value is in range + if (checkValue > maxValue || checkValue < minValue) + { + throw new ConstraintException(ERR_OUT_OF_RANGE, checkValue, minValue, maxValue); + } + } +} diff --git a/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java index 9ffd371821..b71dc06d1c 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java @@ -59,14 +59,37 @@ public class RegexConstraint extends AbstractConstraint } /** - * Set the regular expression used to evaluate string values - * @param expression similar to the {@link String#matches(java.lang.String) argument + * @return Returns the regular expression similar to the {@link String#matches(java.lang.String)} + */ + public String getExpression() + { + return expression; + } + + /** + * Set the regular expression used to evaluate String values + * @param regular expression similar to the {@link String#matches(java.lang.String)} argument */ public void setExpression(String expression) { this.expression = expression; } - + + /** + * @return Returns true if the value must match the regular expression + * or false if the value must not match the regular expression + */ + public boolean getRequiresMatch() + { + return requiresMatch; + } + + /** + * Set whether the regular expression must be matched or not + * + * @param requiresMatch Set to true if the value must match the regular expression + * or false if the value must not match the regular expression + */ public void setRequiresMatch(boolean requiresMatch) { this.requiresMatch = requiresMatch; diff --git a/source/java/org/alfresco/repo/dictionary/constraint/StringLengthConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/StringLengthConstraint.java new file mode 100644 index 0000000000..97c05a0306 --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/constraint/StringLengthConstraint.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.dictionary.constraint; + +import org.alfresco.service.cmr.dictionary.ConstraintException; +import org.alfresco.service.cmr.dictionary.DictionaryException; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.repository.datatype.TypeConversionException; + +/** + * Constraint implementation that ensures that the length of the String value. + * + * @see #setMinLength(int) + * @see #setMaxLength(int) + * + * @author Derek Hulley + */ +public class StringLengthConstraint extends AbstractConstraint +{ + private static final String ERR_INVALID_MIN_LENGTH = "d_dictionary.constraint.string_length.invalid_min_length"; + private static final String ERR_INVALID_MAX_LENGTH = "d_dictionary.constraint.string_length.invalid_max_length"; + private static final String ERR_NON_STRING = "d_dictionary.constraint.string_length.non_string"; + private static final String ERR_INVALID_LENGTH = "d_dictionary.constraint.string_length.invalid_length"; + + private int minLength = 0; + private int maxLength = Integer.MAX_VALUE; + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(80); + sb.append("StringLengthConstraint") + .append("[ minLength=").append(minLength) + .append(", maxLength=").append(maxLength) + .append("]"); + return sb.toString(); + } + + /** + * @return Returns the minimum number of characters allowed + */ + public int getMinLength() + { + return minLength; + } + + /** + * Set the minimum number of characters allowed. Valid values are in + * the range [0, {@link Integer#MAX_VALUE}]. + * + * @param minLength the minimum numbers of characters allowed + */ + public void setMinLength(int minLength) + { + if (minLength > this.maxLength || minLength < 0) + { + throw new DictionaryException(ERR_INVALID_MIN_LENGTH, minLength); + } + this.minLength = minLength; + } + + /** + * @return Returns the maximum number of characters allowed + */ + public int getMaxLength() + { + return maxLength; + } + + /** + * Set the maximum number of characters allowed. Valid values are in + * the range [0, {@link Integer#MAX_VALUE}]. + * + * @param maxLength the minimum numbers of characters allowed + */ + public void setMaxLength(int maxLength) + { + if (maxLength < this.minLength) + { + throw new DictionaryException(ERR_INVALID_MAX_LENGTH, maxLength); + } + this.maxLength = maxLength; + } + + public void initialize() + { + } + + protected void evaluateSingleValue(Object value) + { + // ensure that the value can be converted to a String + String checkValue = null; + try + { + checkValue = DefaultTypeConverter.INSTANCE.convert(String.class, value); + } + catch (TypeConversionException e) + { + throw new ConstraintException(ERR_NON_STRING, value); + } + + // Check that the value length + int length = checkValue.length(); + if (length > maxLength || length < minLength) + { + if (length > 20) + { + checkValue = checkValue.substring(0, 17) + "..."; + } + throw new ConstraintException(ERR_INVALID_LENGTH, checkValue, minLength, maxLength); + } + } +} diff --git a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml index fef9e4f2f0..35a3d4f39c 100644 --- a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml +++ b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml @@ -27,6 +27,14 @@ [A-Z]* false + + 0 + 256 + + + 0 + 256 + @@ -43,6 +51,7 @@ + @@ -165,6 +174,9 @@ true false + + +