diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 5d2a627f23..f21de2a407 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -400,7 +400,7 @@ - + alfresco/model/dictionaryModel.xml diff --git a/config/alfresco/messages/dictionary-messages.properties b/config/alfresco/messages/dictionary-messages.properties index e8d19ddca1..1f326a5fbb 100644 --- a/config/alfresco/messages/dictionary-messages.properties +++ b/config/alfresco/messages/dictionary-messages.properties @@ -4,19 +4,21 @@ d_dictionary.model.err.no_model=Model ''{0}'' does not exist d_dictionary.model.err.type_not_found=Failed to create anonymous type as specified type {0} not found d_dictionary.model.err.aspect_not_found=Failed to create anonymous type as specified aspect {0} not found -d_dictionary.model.err.cyclic_ref=Constraint ''{0}'' is part of a cyclic reference of constraints -d_dictionary.model.err.type_and_ref=Constraint ''{0}'' cannot have a ''type'' and be a ''reference'' attribute -d_dictionary.model.err.type_or_ref=Constraint ''{0}'' cannot have a ''type'' and be a ''reference'' attribute -d_dictionary.model.err.ref_not_found=Constraint reference ''{0}'' not found on constraint ''{1}'' -d_dictionary.model.err.anon_needs_property=Anonymous constraints can only be declared within the context of a property -d_dictionary.model.err.invalid_type=Constraint type ''{0}'' on constraint ''{1}'' is not a well-known type or a valid Constraint implementation -d_dictionary.model.err.construct_failure=Failed to construct an instance of type ''{0}'' for constraint ''{1}'' -d_dictionary.model.err.property_not_set=Property ''{0}'' has not been set on constraint ''{1}'' -d_dictionary.model.err.evaluate_exception=Exception during evaluation of constraint ''{0}'': {1} +d_dictionary.constraint.err.cyclic_ref=Constraint ''{0}'' is part of a cyclic reference of constraints +d_dictionary.constraint.err.type_and_ref=Constraint ''{0}'' cannot have a ''type'' and be a ''reference'' attribute +d_dictionary.constraint.err.type_or_ref=Constraint ''{0}'' cannot have a ''type'' and be a ''reference'' attribute +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.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}'' +d_dictionary.constraint.err.evaluate_exception=Exception during evaluation of constraint ''{0}'': {1} d_dictionary.property.err.property_type_not_specified=Property type of property ''{0}'' must be specified d_dictionary.property.err.property_type_not_found=Property type ''{0}'' of property ''{1}'' is not found d_dictionary.property.err.single_valued_content=Content properties must be single-valued d_dictionary.property.err.duplicate_constraint_on_property=Found duplicate constraint definition ''{0}'' within property ''{1}'' -d_dictionary.constraint.regex.no_match=Value ''{0}'' does not match regular expression: {1} \ No newline at end of file +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 diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index a4ba842360..f099ab2655 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -14,6 +14,12 @@ + + + \<\?\/\:\|\¬\£\%\&\+\;]+]]> + true + + @@ -25,6 +31,9 @@ Name d:text true + + + diff --git a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java index 6498c863ff..9797abb3e2 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java @@ -27,6 +27,7 @@ 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; /** * Compiled Property Constraint @@ -35,13 +36,14 @@ import org.springframework.beans.BeanWrapperImpl; */ /*package*/ class M2ConstraintDefinition implements ConstraintDefinition { - public static final String ERR_CYCLIC_REF = "d_dictionary.model.err.cyclic_ref"; + public static final String ERR_CYCLIC_REF = "d_dictionary.constraint.err.cyclic_ref"; public static final String ERR_TYPE_AND_REF = "d_dictionary.constraint.err.type_and_ref"; public static final String ERR_TYPE_OR_REF = "d_dictionary.constraint.err.type_or_ref"; 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_CONSTRUCT_FAILURE = "d_dictionary.constraint.err.construct_failure"; + public static final String ERR_PROPERTY_MISMATCH = "d_dictionary.constraint.err.property_mismatch"; private static int anonPropCount = 0; @@ -174,7 +176,14 @@ import org.springframework.beans.BeanWrapperImpl; List constraintNamedValues = m2Constraint.getParameters(); for (M2NamedValue namedValue : constraintNamedValues) { - beanWrapper.setPropertyValue(namedValue.getName(), namedValue.getValue()); + try + { + beanWrapper.setPropertyValue(namedValue.getName(), namedValue.getValue()); + } + catch (TypeMismatchException e) + { + throw new DictionaryException(ERR_PROPERTY_MISMATCH, e, namedValue.getName(), shortName); + } } // now initialize constraint.initialize(); diff --git a/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java index 928aca5f8c..71f46ccab6 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java @@ -19,6 +19,7 @@ package org.alfresco.repo.dictionary.constraint; 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; /** @@ -28,8 +29,8 @@ import org.alfresco.service.cmr.dictionary.DictionaryException; */ public abstract class AbstractConstraint implements Constraint { - public static final String ERR_PROP_NOT_SET = "d_dictionary.model.err.property_not_set"; - public static final String ERR_EVALUATE_EXCEPTION = "d_dictionary.model.err.evaluate_exception"; + 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"; /** * @see #evaluateSingleValue(Object) @@ -51,7 +52,7 @@ public abstract class AbstractConstraint implements Constraint evaluateSingleValue(value); } } - catch (DictionaryException e) + catch (ConstraintException e) { // this can go throw e; @@ -82,7 +83,7 @@ public abstract class AbstractConstraint implements Constraint * Support for evaluation of properties. The value passed in will never be a * collection. * - * @throws DictionaryException throw this when the evaluation fails + * @throws ConstraintException throw this when the evaluation fails */ protected abstract void evaluateSingleValue(Object value); } diff --git a/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java b/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java index 652eb92ecd..4966ea2fe2 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java @@ -23,9 +23,11 @@ import junit.framework.TestCase; import org.alfresco.i18n.I18NUtil; import org.alfresco.repo.dictionary.DictionaryDAOTest; -import org.alfresco.service.cmr.dictionary.DictionaryException; +import org.alfresco.service.cmr.dictionary.ConstraintException; /** + * This file must be saved using UTF-8. + * * @see org.alfresco.service.cmr.dictionary.Constraint * @see org.alfresco.repo.dictionary.constraint.AbstractConstraint * @see org.alfresco.repo.dictionary.constraint.RegexConstraint @@ -58,7 +60,7 @@ public class ConstraintsTest extends TestCase constraint.evaluate(dummyObjects); fail("Failed to detected constraint violation in collection"); } - catch (DictionaryException e) + catch (ConstraintException e) { // expected } @@ -70,6 +72,7 @@ public class ConstraintsTest extends TestCase { RegexConstraint constraint = new RegexConstraint(); constraint.setExpression("[A-Z]*"); + constraint.setRequiresMatch(true); constraint.initialize(); // do some successful stuff @@ -82,7 +85,7 @@ public class ConstraintsTest extends TestCase constraint.evaluate("abc"); fail("Regular expression evaluation should have failed: abc"); } - catch (DictionaryException e) + catch (ConstraintException e) { String msg = e.getMessage(); assertFalse("I18N of constraint message failed", msg.startsWith("d_dictionary.constraint")); @@ -96,9 +99,56 @@ public class ConstraintsTest extends TestCase constraint.evaluate(DummyEnum.abc); fail("Regular expression evaluation should have failed for enum: " + DummyEnum.abc); } - catch (DictionaryException e) + catch (ConstraintException e) { } + + // now switch the requiresMatch around + constraint.setRequiresMatch(false); + constraint.initialize(); + + constraint.evaluate(DummyEnum.abc); + } + + public void testRegexConstraintFilename() throws Exception + { + // we assume UTF-8 + String expression = "[^\\\"\\*\\\\\\>\\<\\?\\/\\:\\|\\¬\\£\\%\\&\\+\\;]+"; + String invalidChars = "\"*\\>.txt"); + fail("Failed to detect invalid filename"); + } + catch (ConstraintException e) + { + // expected + } + // ... and a valid one + constraint.evaluate("Company Home"); } private enum DummyEnum @@ -135,7 +185,7 @@ public class ConstraintsTest extends TestCase } else { - throw new DictionaryException("Non-String value"); + throw new ConstraintException("Non-String value"); } } } diff --git a/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java index d7117b1a64..9ffd371821 100644 --- a/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java +++ b/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java @@ -16,7 +16,10 @@ */ package org.alfresco.repo.dictionary.constraint; +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; @@ -25,16 +28,35 @@ import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; * Where possible, the {@link org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter type converter} * will be used to first convert the value to a String, so the evaluation * will be against the value's String equivalent. + *

+ * The failure condition can be changed to occur either on a match or on a non-match by using + * the {@link #setRequiresMatch(boolean) requiresMatch} property. The default is true, i.e. + * failures will occur if the object value does not match the given expression. * * @see java.lang.String#matches(java.lang.String) + * @see java.util.regex.Pattern * * @author Derek Hulley */ public class RegexConstraint extends AbstractConstraint { public static final String CONSTRAINT_REGEX_NO_MATCH = "d_dictionary.constraint.regex.no_match"; + public static final String CONSTRAINT_REGEX_MATCH = "d_dictionary.constraint.regex.match"; private String expression; + private Pattern patternMatcher; + private boolean requiresMatch = true; + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(80); + sb.append("RegexConstraint") + .append("[ expression=").append(expression) + .append(", requiresMatch=").append(requiresMatch) + .append("]"); + return sb.toString(); + } /** * Set the regular expression used to evaluate string values @@ -44,6 +66,11 @@ public class RegexConstraint extends AbstractConstraint { this.expression = expression; } + + public void setRequiresMatch(boolean requiresMatch) + { + this.requiresMatch = requiresMatch; + } public void initialize() { @@ -51,16 +78,25 @@ public class RegexConstraint extends AbstractConstraint { throw new DictionaryException(AbstractConstraint.ERR_PROP_NOT_SET, "expression"); } + this.patternMatcher = Pattern.compile(expression); } - public void evaluateSingleValue(Object value) + protected void evaluateSingleValue(Object value) { // convert the value to a String String valueStr = DefaultTypeConverter.INSTANCE.convert(String.class, value); - boolean matches = valueStr.matches(expression); - if (!matches) + Matcher matcher = patternMatcher.matcher(valueStr); + boolean matches = matcher.matches(); + if (matches != requiresMatch) { - throw new DictionaryException(RegexConstraint.CONSTRAINT_REGEX_NO_MATCH, value, expression); + if (requiresMatch) + { + throw new ConstraintException(RegexConstraint.CONSTRAINT_REGEX_NO_MATCH, value, expression); + } + else + { + throw new ConstraintException(RegexConstraint.CONSTRAINT_REGEX_MATCH, value, expression); + } } } } diff --git a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml index 8365d96ed5..b9f6f4b464 100644 --- a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml +++ b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml @@ -25,6 +25,7 @@ [A-Z]* + false diff --git a/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java b/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java index 65fcb46bf8..c3dc77e811 100644 --- a/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java +++ b/source/java/org/alfresco/repo/node/integrity/PropertiesIntegrityEvent.java @@ -23,10 +23,12 @@ import java.util.Map; import java.util.Set; import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.Constraint; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; +import org.alfresco.service.cmr.dictionary.ConstraintException; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; @@ -52,11 +54,8 @@ public class PropertiesIntegrityEvent extends AbstractIntegrityEvent public void checkIntegrity(List eventResults) { - try - { - checkAllProperties(getNodeRef(), eventResults); - } - catch (InvalidNodeRefException e) + NodeRef nodeRef = getNodeRef(); + if (!nodeService.exists(nodeRef)) { // node has gone if (logger.isDebugEnabled()) @@ -66,6 +65,10 @@ public class PropertiesIntegrityEvent extends AbstractIntegrityEvent eventResults.clear(); return; } + else + { + checkAllProperties(getNodeRef(), eventResults); + } } /** @@ -123,7 +126,6 @@ public class PropertiesIntegrityEvent extends AbstractIntegrityEvent for (PropertyDefinition propertyDef : propertyDefs) { QName propertyQName = propertyDef.getName(); - Serializable propertyValue = nodeProperties.get(propertyQName); // check that mandatory properties are set if (propertyDef.isMandatory() && !nodeProperties.containsKey(propertyQName)) { @@ -136,7 +138,30 @@ public class PropertiesIntegrityEvent extends AbstractIntegrityEvent // next one continue; } - // TODO: Incorporate value constraint checks - JIRA AR166 + Serializable propertyValue = nodeProperties.get(propertyQName); + // check constraints + List constraintDefs = propertyDef.getConstraints(); + for (ConstraintDefinition constraintDef : constraintDefs) + { + // get the constraint implementation + Constraint constraint = constraintDef.getConstraint(); + try + { + constraint.evaluate(propertyValue); + } + catch (ConstraintException e) + { + IntegrityRecord result = new IntegrityRecord( + "Invalid property value: \n" + + " Node: " + nodeRef + "\n" + + " Type: " + typeQName + "\n" + + " Property: " + propertyQName + "\n" + + " Constraint: " + e.getMessage()); + eventResults.add(result); + // next one + continue; + } + } } } } diff --git a/source/java/org/alfresco/service/cmr/dictionary/DictionaryException.java b/source/java/org/alfresco/service/cmr/dictionary/DictionaryException.java index b9d3c6dbbf..6c60a27691 100644 --- a/source/java/org/alfresco/service/cmr/dictionary/DictionaryException.java +++ b/source/java/org/alfresco/service/cmr/dictionary/DictionaryException.java @@ -27,14 +27,14 @@ public class DictionaryException extends AlfrescoRuntimeException { private static final long serialVersionUID = 3257008761007847733L; - public DictionaryException(String msg) + public DictionaryException(String msgId) { - super(msg); + super(msgId); } - public DictionaryException(String msg, Throwable cause) + public DictionaryException(String msgId, Throwable cause) { - super(msg, cause); + super(msgId, cause); } public DictionaryException(String msgId, Object ... args)