diff --git a/config/alfresco/messages/dictionary-messages.properties b/config/alfresco/messages/dictionary-messages.properties index 2de0873860..f232cb6b60 100644 --- a/config/alfresco/messages/dictionary-messages.properties +++ b/config/alfresco/messages/dictionary-messages.properties @@ -10,6 +10,7 @@ d_dictionary.constraint.err.type_or_ref=Constraint ''{0}'' cannot have a ''type' d_dictionary.constraint.err.ref_not_found=Constraint reference ''{0}'' not found on constraint ''{1}'' d_dictionary.constraint.err.anon_needs_property=Anonymous constraints can only be declared within the context of a property d_dictionary.constraint.err.invalid_type=Constraint type ''{0}'' on constraint ''{1}'' is not a well-known type or a valid Constraint implementation +d_dictionary.constraint.err.property_simple_and_list="Constraint ''{0}'' has both a simple and list value for property ''{1}'' d_dictionary.constraint.err.construct_failure=Failed to construct an instance of type ''{0}'' for constraint ''{1}'' d_dictionary.constraint.err.property_mismatch=Property mismatch setting property ''{0}'' on constraint ''{1}'' d_dictionary.constraint.err.property_not_set=Property ''{0}'' has not been set on constraint ''{1}'' @@ -34,3 +35,7 @@ d_dictionary.constraint.string_length.invalid_min_length=Invalid ''minLength'' p 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}] + +d_dictionary.constraint.list_of_values.no_values=The list of allowed values is empty +d_dictionary.constraint.list_of_values.non_string=Property value could not be converted to a String: {0} +d_dictionary.constraint.list_of_values.invalid_value=The value is not an allowed value: {0} diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index 9b8d61686c..21e3a7b5bf 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -16,8 +16,8 @@ - \<\?\/\:\|\¬\£\%\&\+\;]+]]> - true + \<\?\/\:\|\¬\£\%\&\+\;]+]]> + true diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java index 913e4dc11a..3fc1f8de32 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java @@ -126,7 +126,7 @@ public class DictionaryDAOTest extends TestCase // get the constraints for a property without constraints QName propNoConstraintsQName = QName.createQName(TEST_URL, "fileprop"); PropertyDefinition propNoConstraintsDef = service.getProperty(propNoConstraintsQName); - assertNotNull("Property without constraints returned empty list", propNoConstraintsDef.getConstraints()); + assertNotNull("Property without constraints returned null list", propNoConstraintsDef.getConstraints()); // get the constraints defined for the property QName prop1QName = QName.createQName(TEST_URL, "prop1"); diff --git a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java index b87e845bea..a6a3cde0c4 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java @@ -18,6 +18,7 @@ package org.alfresco.repo.dictionary; import java.util.List; +import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint; import org.alfresco.repo.dictionary.constraint.NumericRangeConstraint; import org.alfresco.repo.dictionary.constraint.RegexConstraint; import org.alfresco.repo.dictionary.constraint.StringLengthConstraint; @@ -29,7 +30,8 @@ import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.QName; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; -import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.InvalidPropertyException; +import org.springframework.beans.PropertyAccessException; /** * Compiled Property Constraint @@ -44,6 +46,7 @@ import org.springframework.beans.TypeMismatchException; public static final String ERR_REF_NOT_FOUND = "d_dictionary.constraint.err.ref_not_found"; public static final String ERR_ANON_NEEDS_PROPERTY = "d_dictionary.constraint.err.anon_needs_property"; public static final String ERR_INVALID_TYPE = "d_dictionary.constraint.err.invalid_type"; + public static final String ERR_SIMPLE_AND_LIST = "d_dictionary.constraint.err.property_simple_and_list"; public static final String ERR_CONSTRUCT_FAILURE = "d_dictionary.constraint.err.construct_failure"; public static final String ERR_PROPERTY_MISMATCH = "d_dictionary.constraint.err.property_mismatch"; @@ -178,11 +181,28 @@ import org.springframework.beans.TypeMismatchException; List constraintNamedValues = m2Constraint.getParameters(); for (M2NamedValue namedValue : constraintNamedValues) { + Object value = null; + if (namedValue.getSimpleValue() != null && namedValue.getListValue() != null) + { + throw new DictionaryException(ERR_SIMPLE_AND_LIST, shortName, namedValue.getName()); + } + else if (namedValue.getSimpleValue() != null) + { + value = namedValue.getSimpleValue(); + } + else if (namedValue.getListValue() != null) + { + value = namedValue.getListValue(); + } try { - beanWrapper.setPropertyValue(namedValue.getName(), namedValue.getValue()); + beanWrapper.setPropertyValue(namedValue.getName(), value); } - catch (TypeMismatchException e) + catch (PropertyAccessException e) + { + throw new DictionaryException(ERR_PROPERTY_MISMATCH, e, namedValue.getName(), shortName); + } + catch (InvalidPropertyException e) { throw new DictionaryException(ERR_PROPERTY_MISMATCH, e, namedValue.getName(), shortName); } @@ -244,6 +264,14 @@ import org.springframework.beans.TypeMismatchException; { return new StringLengthConstraint(); } + }, + LIST + { + @Override + protected Constraint newInstance() + { + return new ListOfValuesConstraint(); + } }; /** diff --git a/source/java/org/alfresco/repo/dictionary/M2NamedValue.java b/source/java/org/alfresco/repo/dictionary/M2NamedValue.java index 6c2e7918ab..a50d9b08ce 100644 --- a/source/java/org/alfresco/repo/dictionary/M2NamedValue.java +++ b/source/java/org/alfresco/repo/dictionary/M2NamedValue.java @@ -16,6 +16,8 @@ */ package org.alfresco.repo.dictionary; +import java.util.List; + /** * Definition of a named value that can be used for property injection. * @@ -24,8 +26,8 @@ package org.alfresco.repo.dictionary; public class M2NamedValue { private String name; - private String value; - + private String simpleValue; + private List listValue; /*package*/ M2NamedValue() { @@ -35,7 +37,7 @@ public class M2NamedValue @Override public String toString() { - return (name + "=" + value); + return (name + "=" + (simpleValue == null ? listValue : simpleValue)); } public String getName() @@ -46,8 +48,16 @@ public class M2NamedValue /** * @return Returns the raw, unconverted value */ - public String getValue() + public String getSimpleValue() { - return value; + return simpleValue; + } + + /** + * @return Returns the list of raw, unconverted values + */ + public List getListValue() + { + return listValue; } } diff --git a/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java index 39dc1077c2..f4c343a4ed 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java @@ -33,6 +33,22 @@ public abstract class AbstractConstraint implements Constraint public static final String ERR_PROP_NOT_SET = "d_dictionary.constraint.err.property_not_set"; public static final String ERR_EVALUATE_EXCEPTION = "d_dictionary.constraint.err.evaluate_exception"; + /** + * Check that the given value is not null. + * + * @param name the name of the property + * @param value the value to check for null + * + * @throws DictionaryException if the the property is null + */ + protected void checkPropertyNotNull(String name, Object value) + { + if (value == null) + { + throw new DictionaryException(AbstractConstraint.ERR_PROP_NOT_SET, value); + } + } + /** * @see #evaluateSingleValue(Object) * @see #evaluateCollection(Collection) diff --git a/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java b/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java index d46d60f1e4..7905dd077c 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java @@ -18,6 +18,7 @@ package org.alfresco.repo.dictionary.constraint; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import junit.framework.TestCase; @@ -148,6 +149,32 @@ public class ConstraintsTest extends TestCase evaluate(constraint, Arrays.asList("abc", "abcdefg"), true); } + public void testListOfValuesConstraint() throws Exception + { + ListOfValuesConstraint constraint = new ListOfValuesConstraint(); + try + { + constraint.setAllowedValues(Collections.emptyList()); + } + catch (DictionaryException e) + { + // expected + checkI18NofExceptionMessage(e); + } + List allowedValues = Arrays.asList(new String[] {"abc", "def", "ghi"}); + constraint.setAllowedValues(allowedValues); + + evaluate(constraint, "def", false); + evaluate(constraint, "DEF", true); + evaluate(constraint, Arrays.asList("abc", "def"), false); + evaluate(constraint, Arrays.asList("abc", "DEF"), true); + + // now make it case-insensitive + constraint.setCaseSensitive(false); + evaluate(constraint, "DEF", false); + evaluate(constraint, Arrays.asList("abc", "DEF"), false); + } + public void testNumericRangeConstraint() throws Exception { NumericRangeConstraint constraint = new NumericRangeConstraint(); diff --git a/source/java/org/alfresco/repo/dictionary/constraint/ListOfValuesConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/ListOfValuesConstraint.java new file mode 100644 index 0000000000..7ebdddf6f5 --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/constraint/ListOfValuesConstraint.java @@ -0,0 +1,152 @@ +/* + * 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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +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 the value is one of a constrained + * list of values. By default, this constraint is case-sensitive. + * + * @see #setAllowedValues(List) + * @see #setCaseSensitive(boolean) + * + * @author Derek Hulley + */ +public class ListOfValuesConstraint extends AbstractConstraint +{ + private static final String ERR_NO_VALUES = "d_dictionary.constraint.list_of_values.no_values"; + private static final String ERR_NON_STRING = "d_dictionary.constraint.string_length.non_string"; + private static final String ERR_INVALID_VALUE = "d_dictionary.constraint.list_of_values.invalid_value"; + + private List allowedValues; + private List allowedValuesUpper; + private boolean caseSensitive; + + public ListOfValuesConstraint() + { + caseSensitive = true; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(80); + sb.append("ListOfValuesConstraint") + .append("[ allowedValues=").append(allowedValues) + .append(", caseSensitive=").append(caseSensitive) + .append("]"); + return sb.toString(); + } + + /** + * Get the allowed values. Note that these are String instances, but may + * represent non-String values. It is up to the caller to distinguish. + * + * @return Returns the values allowed + */ + public List getAllowedValues() + { + return allowedValues; + } + + /** + * Set the values that are allowed by the constraint. + * + * @param values a list of allowed values + */ + @SuppressWarnings("unchecked") + public void setAllowedValues(List allowedValues) + { + if (allowedValues == null) + { + throw new DictionaryException(ERR_NO_VALUES); + } + int valueCount = allowedValues.size(); + if (valueCount == 0) + { + throw new DictionaryException(ERR_NO_VALUES); + } + this.allowedValues = Collections.unmodifiableList(allowedValues); + // make the upper case versions + this.allowedValuesUpper = new ArrayList(valueCount); + for (String allowedValue : this.allowedValues) + { + allowedValuesUpper.add(allowedValue.toUpperCase()); + } + } + + /** + * @return Returns true if this constraint is case-sensitive (default) + */ + public boolean isCaseSensitive() + { + return caseSensitive; + } + + /** + * Set the handling of case checking. + * + * @param caseSensitive true if the constraint is case-sensitive (default), + * or false for case-insensitive. + */ + public void setCaseSensitive(boolean caseSensitive) + { + this.caseSensitive = caseSensitive; + } + + public void initialize() + { + checkPropertyNotNull("allowedValues", allowedValues); + } + + protected void evaluateSingleValue(Object value) + { + // convert the value to a String + String valueStr = null; + try + { + valueStr = DefaultTypeConverter.INSTANCE.convert(String.class, value); + } + catch (TypeConversionException e) + { + throw new ConstraintException(ERR_NON_STRING, value); + } + // check that the value is in the set of allowed values + if (caseSensitive) + { + if (!allowedValues.contains(valueStr)) + { + throw new ConstraintException(ERR_INVALID_VALUE, value); + } + } + else + { + if (!allowedValuesUpper.contains(valueStr.toUpperCase())) + { + throw new ConstraintException(ERR_INVALID_VALUE, value); + } + } + } +} diff --git a/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java index b71dc06d1c..035a5ba049 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java @@ -20,7 +20,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.alfresco.service.cmr.dictionary.ConstraintException; -import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; /** @@ -94,13 +93,11 @@ public class RegexConstraint extends AbstractConstraint { this.requiresMatch = requiresMatch; } - + public void initialize() { - if (expression == null) - { - throw new DictionaryException(AbstractConstraint.ERR_PROP_NOT_SET, "expression"); - } + checkPropertyNotNull("expression", expression); + this.patternMatcher = Pattern.compile(expression); } diff --git a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml index 35a3d4f39c..1a1113310f 100644 --- a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml +++ b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml @@ -24,16 +24,25 @@ - [A-Z]* - false + [A-Z]* + false - 0 - 256 + 0 + 256 - 0 - 256 + 0 + 256 + + + + + ABC + DEF + + + true diff --git a/source/java/org/alfresco/repo/dictionary/m2binding.xml b/source/java/org/alfresco/repo/dictionary/m2binding.xml index a14f27f488..90459e7b59 100644 --- a/source/java/org/alfresco/repo/dictionary/m2binding.xml +++ b/source/java/org/alfresco/repo/dictionary/m2binding.xml @@ -119,7 +119,16 @@ - + + + + + + + + + +