From 783de4f80a5c150b8c70bb3b6da81aa10b1db9d2 Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Tue, 14 Mar 2006 22:57:50 +0000 Subject: [PATCH] Property constraint dictionary support Constraint implementation support Regular expression constraint git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2547 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/core-services-context.xml | 1 + .../messages/dictionary-messages.properties | 22 ++ .../repo/dictionary/CompiledModel.java | 39 ++- .../repo/dictionary/DelegateModelQuery.java | 15 +- .../repo/dictionary/DictionaryDAOImpl.java | 12 +- .../repo/dictionary/DictionaryDAOTest.java | 58 ++++- .../repo/dictionary/M2ClassDefinition.java | 8 +- .../repo/dictionary/M2Constraint.java | 74 ++++++ .../dictionary/M2ConstraintDefinition.java | 227 ++++++++++++++++++ .../org/alfresco/repo/dictionary/M2Model.java | 9 +- .../repo/dictionary/M2NamedValue.java | 53 ++++ .../alfresco/repo/dictionary/M2Property.java | 19 +- .../repo/dictionary/M2PropertyDefinition.java | 95 ++++++-- .../alfresco/repo/dictionary/ModelQuery.java | 9 + .../constraint/AbstractConstraint.java | 88 +++++++ .../constraint/ConstraintsTest.java | 142 +++++++++++ .../constraint/RegexConstraint.java | 66 +++++ .../dictionary/dictionarydaotest_model.xml | 9 + .../alfresco/repo/dictionary/m2binding.xml | 22 ++ .../service/cmr/dictionary/Constraint.java | 56 +++++ .../cmr/dictionary/ConstraintDefinition.java | 42 ++++ .../cmr/dictionary/ConstraintException.java | 34 +++ .../cmr/dictionary/DictionaryException.java | 12 +- .../cmr/dictionary/PropertyDefinition.java | 8 + 24 files changed, 1066 insertions(+), 54 deletions(-) create mode 100644 config/alfresco/messages/dictionary-messages.properties create mode 100644 source/java/org/alfresco/repo/dictionary/M2Constraint.java create mode 100644 source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java create mode 100644 source/java/org/alfresco/repo/dictionary/M2NamedValue.java create mode 100644 source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java create mode 100644 source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java create mode 100644 source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java create mode 100644 source/java/org/alfresco/service/cmr/dictionary/Constraint.java create mode 100644 source/java/org/alfresco/service/cmr/dictionary/ConstraintDefinition.java create mode 100644 source/java/org/alfresco/service/cmr/dictionary/ConstraintException.java diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 35a5eaa976..5d2a627f23 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -71,6 +71,7 @@ alfresco.messages.template-service alfresco.messages.lock-service alfresco.messages.patch-service + alfresco.messages.dictionary-messages diff --git a/config/alfresco/messages/dictionary-messages.properties b/config/alfresco/messages/dictionary-messages.properties new file mode 100644 index 0000000000..e8d19ddca1 --- /dev/null +++ b/config/alfresco/messages/dictionary-messages.properties @@ -0,0 +1,22 @@ +# Dictionary-related messages + +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.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 diff --git a/source/java/org/alfresco/repo/dictionary/CompiledModel.java b/source/java/org/alfresco/repo/dictionary/CompiledModel.java index 56f2ea9b7a..c665de6596 100644 --- a/source/java/org/alfresco/repo/dictionary/CompiledModel.java +++ b/source/java/org/alfresco/repo/dictionary/CompiledModel.java @@ -26,6 +26,7 @@ import java.util.TreeMap; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.ModelDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; @@ -66,7 +67,7 @@ import org.apache.commons.logging.LogFactory; private Map aspects = new HashMap(); private Map properties = new HashMap(); private Map associations = new HashMap(); - + private Map constraints = new HashMap(); /** * Construct @@ -82,11 +83,11 @@ import org.apache.commons.logging.LogFactory; // Phase 1: Construct model definitions from model entries // resolving qualified names this.model = model; - constructDefinitions(model, dictionaryDAO, namespaceDAO); + constructDefinitions(model, namespaceDAO); // Phase 2: Resolve dependencies between model definitions ModelQuery query = new DelegateModelQuery(this, dictionaryDAO); - resolveDependencies(query); + resolveDependencies(query, namespaceDAO); // Phase 3: Resolve inheritance of values within class hierachy resolveInheritance(query); @@ -111,10 +112,9 @@ import org.apache.commons.logging.LogFactory; * Construct compiled definitions * * @param model model definition - * @param dictionaryDAO dictionary DAO * @param namespaceDAO namespace DAO */ - private void constructDefinitions(M2Model model, DictionaryDAO dictionaryDAO, NamespaceDAO namespaceDAO) + private void constructDefinitions(M2Model model, NamespaceDAO namespaceDAO) { NamespacePrefixResolver localPrefixes = createLocalPrefixResolver(model, namespaceDAO); @@ -155,6 +155,18 @@ import org.apache.commons.logging.LogFactory; classes.put(def.getName(), def); aspects.put(def.getName(), def); } + + // Construct Constraint Definitions + for (M2Constraint constraint : model.getConstraints()) + { + M2ConstraintDefinition def = new M2ConstraintDefinition(modelDefinition, null, constraint, localPrefixes); + QName qname = def.getName(); + if (constraints.containsKey(qname)) + { + throw new DictionaryException("Found duplicate constraint definition " + constraint.getName() + " (an aspect)"); + } + constraints.put(qname, def); + } } @@ -196,15 +208,21 @@ import org.apache.commons.logging.LogFactory; * * @param query support for querying other items in model */ - private void resolveDependencies(ModelQuery query) + private void resolveDependencies(ModelQuery query, NamespaceDAO namespaceDAO) { + NamespacePrefixResolver prefixResolver = createLocalPrefixResolver(model, namespaceDAO); + for (DataTypeDefinition def : dataTypes.values()) { ((M2DataTypeDefinition)def).resolveDependencies(query); } for (ClassDefinition def : classes.values()) { - ((M2ClassDefinition)def).resolveDependencies(query); + ((M2ClassDefinition)def).resolveDependencies(query, prefixResolver, constraints); + } + for (ConstraintDefinition def : constraints.values()) + { + ((M2ConstraintDefinition)def).resolveDependencies(query); } } @@ -363,4 +381,11 @@ import org.apache.commons.logging.LogFactory; return associations.get(name); } + /* (non-Javadoc) + * @see org.alfresco.repo.dictionary.impl.ModelQuery#getConstraint(QName) + */ + public ConstraintDefinition getConstraint(QName name) + { + return constraints.get(name); + } } diff --git a/source/java/org/alfresco/repo/dictionary/DelegateModelQuery.java b/source/java/org/alfresco/repo/dictionary/DelegateModelQuery.java index 4a8931e443..62ff03214e 100644 --- a/source/java/org/alfresco/repo/dictionary/DelegateModelQuery.java +++ b/source/java/org/alfresco/repo/dictionary/DelegateModelQuery.java @@ -19,6 +19,7 @@ package org.alfresco.repo.dictionary; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; @@ -147,5 +148,17 @@ import org.alfresco.service.namespace.QName; } return def; } - + + /* (non-Javadoc) + * @see ModelQuery#getConstraint(QName) + */ + public ConstraintDefinition getConstraint(QName name) + { + ConstraintDefinition def = query.getConstraint(name); + if (def == null) + { + def = delegate.getConstraint(name); + } + return def; + } } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java index 374ba21bcf..3a81f40e07 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java @@ -24,6 +24,7 @@ import java.util.Map; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.ModelDefinition; @@ -144,7 +145,7 @@ public class DictionaryDAOImpl implements DictionaryDAO if (model == null) { // TODO: Load model from persistent store - throw new DictionaryException("Model " + modelName + " does not exist"); + throw new DictionaryException("d_dictionary.model.err.no_model", modelName); } return model; } @@ -226,6 +227,11 @@ public class DictionaryDAOImpl implements DictionaryDAO return (model == null) ? null : model.getProperty(propertyName); } + public ConstraintDefinition getConstraint(QName constraintQName) + { + CompiledModel model = getCompiledModelForNamespace(constraintQName.getNamespaceURI()); + return (model == null) ? null : model.getConstraint(constraintQName); + } /* (non-Javadoc) * @see org.alfresco.repo.dictionary.impl.ModelQuery#getAssociation(org.alfresco.repo.ref.QName) @@ -288,7 +294,7 @@ public class DictionaryDAOImpl implements DictionaryDAO TypeDefinition typeDef = getType(type); if (typeDef == null) { - throw new DictionaryException("Failed to create anonymous type as specified type " + type + " not found"); + throw new DictionaryException("d_dictionary.model.err.type_not_found", type); } Collection aspectDefs = new ArrayList(); if (aspects != null) @@ -298,7 +304,7 @@ public class DictionaryDAOImpl implements DictionaryDAO AspectDefinition aspectDef = getAspect(aspect); if (typeDef == null) { - throw new DictionaryException("Failed to create anonymous type as specified aspect " + aspect + " not found"); + throw new DictionaryException("d_dictionary.model.err.aspect_not_found", aspect); } aspectDefs.add(aspectDef); } diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java index 00b804f8ce..5c24a4aca0 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java @@ -21,7 +21,11 @@ import java.util.List; import junit.framework.TestCase; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.dictionary.constraint.RegexConstraint; import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.Constraint; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.InvalidTypeException; @@ -33,15 +37,20 @@ import org.alfresco.service.namespace.QName; public class DictionaryDAOTest extends TestCase { - + public static final String TEST_RESOURCE_MESSAGES = "alfresco/messages/dictionary-messages"; + + private static final String TEST_URL = "http://www.alfresco.org/test/dictionarydaotest/1.0"; private static final String TEST_MODEL = "org/alfresco/repo/dictionary/dictionarydaotest_model.xml"; private static final String TEST_BUNDLE = "org/alfresco/repo/dictionary/dictionarydaotest_model"; - private DictionaryService service; + private DictionaryService service; @Override public void setUp() { + // register resource bundles for messages + I18NUtil.registerResourceBundle(TEST_RESOURCE_MESSAGES); + // Instantiate Dictionary Service NamespaceDAO namespaceDAO = new NamespaceDAOImpl(); DictionaryDAOImpl dictionaryDAO = new DictionaryDAOImpl(namespaceDAO); @@ -90,34 +99,59 @@ public class DictionaryDAOTest extends TestCase public void testLabels() { - QName model = QName.createQName("http://www.alfresco.org/test/dictionarydaotest/1.0", "dictionarydaotest"); + QName model = QName.createQName(TEST_URL, "dictionarydaotest"); ModelDefinition modelDef = service.getModel(model); assertEquals("Model Description", modelDef.getDescription()); - QName type = QName.createQName("http://www.alfresco.org/test/dictionarydaotest/1.0", "base"); + QName type = QName.createQName(TEST_URL, "base"); TypeDefinition typeDef = service.getType(type); assertEquals("Base Title", typeDef.getTitle()); assertEquals("Base Description", typeDef.getDescription()); - QName prop = QName.createQName("http://www.alfresco.org/test/dictionarydaotest/1.0", "prop1"); + QName prop = QName.createQName(TEST_URL, "prop1"); PropertyDefinition propDef = service.getProperty(prop); assertEquals("Prop1 Title", propDef.getTitle()); assertEquals("Prop1 Description", propDef.getDescription()); - QName assoc = QName.createQName("http://www.alfresco.org/test/dictionarydaotest/1.0", "assoc1"); + QName assoc = QName.createQName(TEST_URL, "assoc1"); AssociationDefinition assocDef = service.getAssociation(assoc); assertEquals("Assoc1 Title", assocDef.getTitle()); assertEquals("Assoc1 Description", assocDef.getDescription()); - QName datatype = QName.createQName("http://www.alfresco.org/test/dictionarydaotest/1.0", "datatype"); + QName datatype = QName.createQName(TEST_URL, "datatype"); DataTypeDefinition datatypeDef = service.getDataType(datatype); assertEquals("Datatype Analyser", datatypeDef.getAnalyserClassName()); } + public void testConstraints() + { + // 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()); + + // get the constraints defined for the property + QName prop1QName = QName.createQName(TEST_URL, "prop1"); + PropertyDefinition propDef = service.getProperty(prop1QName); + List constraints = propDef.getConstraints(); + assertNotNull("Null constraints list", constraints); + assertEquals("Incorrect number of constraints", 1, constraints.size()); + + // check the individual constraints + ConstraintDefinition constraintDef = constraints.get(0); + assertTrue("Constraint anonymous name incorrect", constraintDef.getName().getLocalName().startsWith("prop1_anon")); + // check that the constraint implementation is valid (it used a reference) + Constraint constraint = constraintDef.getConstraint(); + assertNotNull("Reference constraint has no implementation", constraint); + + // make sure it is the correct type of constraint + assertTrue("Expected type REGEX constraint", constraint instanceof RegexConstraint); + } + public void testSubClassOf() { - QName invalid = QName.createQName("http://www.alfresco.org/test/dictionarydaotest/1.0", "invalid"); - QName base = QName.createQName("http://www.alfresco.org/test/dictionarydaotest/1.0", "base"); - QName file = QName.createQName("http://www.alfresco.org/test/dictionarydaotest/1.0", "file"); - QName folder = QName.createQName("http://www.alfresco.org/test/dictionarydaotest/1.0", "folder"); - QName referenceable = QName.createQName("http://www.alfresco.org/test/dictionarydaotest/1.0", "referenceable"); + QName invalid = QName.createQName(TEST_URL, "invalid"); + QName base = QName.createQName(TEST_URL, "base"); + QName file = QName.createQName(TEST_URL, "file"); + QName folder = QName.createQName(TEST_URL, "folder"); + QName referenceable = QName.createQName(TEST_URL, "referenceable"); // Test invalid args try diff --git a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java index da034c42d4..2f8e407c13 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java @@ -27,6 +27,7 @@ import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.ModelDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; @@ -166,7 +167,10 @@ import org.alfresco.service.namespace.QName; } - /*package*/ void resolveDependencies(ModelQuery query) + /*package*/ void resolveDependencies( + ModelQuery query, + NamespacePrefixResolver prefixResolver, + Map modelConstraints) { if (parentName != null) { @@ -179,7 +183,7 @@ import org.alfresco.service.namespace.QName; for (PropertyDefinition def : properties.values()) { - ((M2PropertyDefinition)def).resolveDependencies(query); + ((M2PropertyDefinition)def).resolveDependencies(query, prefixResolver, modelConstraints); } for (AssociationDefinition def : associations.values()) { diff --git a/source/java/org/alfresco/repo/dictionary/M2Constraint.java b/source/java/org/alfresco/repo/dictionary/M2Constraint.java new file mode 100644 index 0000000000..6da1b4689a --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/M2Constraint.java @@ -0,0 +1,74 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.List; + +/** + * Abstract Property Constraint. + * + * @author Derek Hulley + */ +public class M2Constraint +{ + private String name; + private String ref; + private String type; + private String description; + private List parameters = new ArrayList(2); + + /*package*/ M2Constraint() + { + } + + @Override + public String toString() + { + return this.name; + } + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getRef() + { + return ref; + } + + public String getType() + { + return type; + } + + public String getDescription() + { + return description; + } + + public List getParameters() + { + return parameters; + } +} diff --git a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java new file mode 100644 index 0000000000..6498c863ff --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java @@ -0,0 +1,227 @@ +/* + * 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; + +import java.util.List; + +import org.alfresco.repo.dictionary.constraint.RegexConstraint; +import org.alfresco.service.cmr.dictionary.Constraint; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryException; +import org.alfresco.service.cmr.dictionary.ModelDefinition; +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.QName; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; + +/** + * Compiled Property Constraint + * + * @author Derek Hulley + */ +/*package*/ class M2ConstraintDefinition implements ConstraintDefinition +{ + public static final String ERR_CYCLIC_REF = "d_dictionary.model.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"; + + private static int anonPropCount = 0; + + private ModelDefinition model; + private NamespacePrefixResolver prefixResolver; + private M2Constraint m2Constraint; + private QName name; + private Constraint constraint; + private boolean resolving; + + /*package*/ M2ConstraintDefinition( + M2PropertyDefinition m2PropertyDef, + M2Constraint m2Constraint, + NamespacePrefixResolver prefixResolver) + { + this(m2PropertyDef.getModel(), m2PropertyDef, m2Constraint, prefixResolver); + } + + /*package*/ M2ConstraintDefinition( + ModelDefinition modelDefinition, + M2PropertyDefinition m2PropertyDef, + M2Constraint m2Constraint, + NamespacePrefixResolver prefixResolver) + { + this.model = modelDefinition; + this.m2Constraint = m2Constraint; + this.prefixResolver = prefixResolver; + + String constraintName = m2Constraint.getName(); + if (constraintName == null) + { + // the constraint is anonymous, so it has to be defined within the context of a property + if (m2PropertyDef == null) + { + throw new DictionaryException(ERR_ANON_NEEDS_PROPERTY); + } + // pick the name up from the property and some anonymous value + String localName = m2PropertyDef.getName().getLocalName() + "_anon_" + (++anonPropCount); + this.name = QName.createQName(m2PropertyDef.getName().getNamespaceURI(), localName); + m2Constraint.setName(localName); + } + else + { + this.name = QName.createQName(m2Constraint.getName(), prefixResolver); + } + } + + /*package*/ synchronized void resolveDependencies(ModelQuery query) + { + if (resolving) + { + throw new DictionaryException(ERR_CYCLIC_REF, name.toPrefixString()); + } + // prevent circular references + try + { + resolving = true; + resolveInternal(query); + } + finally + { + resolving = false; + } + } + + private synchronized void resolveInternal(ModelQuery query) + { + if (constraint != null) + { + // already been resolved + return; + } + String shortName = name.toPrefixString(); + String ref = m2Constraint.getRef(); + String type = m2Constraint.getType(); + if (ref != null && type != null) + { + throw new DictionaryException(ERR_TYPE_AND_REF, shortName); + } + else if (ref == null && type == null) + { + throw new DictionaryException(ERR_TYPE_OR_REF, shortName); + } + else if (ref != null) + { + // resolve the reference name + QName qnameRef = QName.createQName(ref, prefixResolver); + // ensure that the reference exists in the model + M2ConstraintDefinition constraintDef = (M2ConstraintDefinition) query.getConstraint(qnameRef); + if (constraintDef == null) + { + throw new DictionaryException(ERR_REF_NOT_FOUND, ref, shortName); + } + // make sure that the constraint definition has itself been resolved + constraintDef.resolveDependencies(query); + // just use the constraint provided by the referenced definition + this.constraint = constraintDef.getConstraint(); + } + else + { + // we have to build the constraint from the type + try + { + ConstraintType constraintType = ConstraintType.valueOf(type); + constraint = constraintType.newInstance(); + } + catch (IllegalArgumentException e) + { + // try to establish it as a class + try + { + Class clazz = Class.forName(type); + constraint = (Constraint) clazz.newInstance(); + } + catch (ClassNotFoundException ee) + { + throw new DictionaryException(ERR_INVALID_TYPE, type, shortName); + } + catch (ClassCastException ee) + { + throw new DictionaryException(ERR_INVALID_TYPE, type, shortName); + } + catch (Exception ee) + { + throw new DictionaryException(ERR_CONSTRUCT_FAILURE, type, shortName); + } + } + // property setters + BeanWrapper beanWrapper = new BeanWrapperImpl(constraint); + List constraintNamedValues = m2Constraint.getParameters(); + for (M2NamedValue namedValue : constraintNamedValues) + { + beanWrapper.setPropertyValue(namedValue.getName(), namedValue.getValue()); + } + // now initialize + constraint.initialize(); + } + } + + /** + * @see #getName() + */ + @Override + public String toString() + { + return getName().toString(); + } + + public ModelDefinition getModel() + { + return model; + } + + public QName getName() + { + return name; + } + + public Constraint getConstraint() + { + return constraint; + } + + /** + * Well-known constraint types + */ + public static enum ConstraintType + { + REGEX + { + @Override + protected Constraint newInstance() + { + return new RegexConstraint(); + } + }; + + /** + * @return Returns the constraint implementation + */ + protected abstract Constraint newInstance(); + } +} diff --git a/source/java/org/alfresco/repo/dictionary/M2Model.java b/source/java/org/alfresco/repo/dictionary/M2Model.java index 384c534a1f..4dad74222a 100644 --- a/source/java/org/alfresco/repo/dictionary/M2Model.java +++ b/source/java/org/alfresco/repo/dictionary/M2Model.java @@ -50,7 +50,7 @@ public class M2Model private List dataTypes = new ArrayList(); private List types = new ArrayList(); private List aspects = new ArrayList(); - + private List constraints = new ArrayList(); private M2Model() { @@ -379,8 +379,13 @@ public class M2Model return null; } - + public List getConstraints() + { + return Collections.unmodifiableList(constraints); + } + // Do not delete: referenced by m2binding.xml + @SuppressWarnings("unused") private static List createList() { return new ArrayList(); diff --git a/source/java/org/alfresco/repo/dictionary/M2NamedValue.java b/source/java/org/alfresco/repo/dictionary/M2NamedValue.java new file mode 100644 index 0000000000..6c2e7918ab --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/M2NamedValue.java @@ -0,0 +1,53 @@ +/* + * 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; + +/** + * Definition of a named value that can be used for property injection. + * + * @author Derek Hulley + */ +public class M2NamedValue +{ + private String name; + private String value; + + + /*package*/ M2NamedValue() + { + } + + + @Override + public String toString() + { + return (name + "=" + value); + } + + public String getName() + { + return name; + } + + /** + * @return Returns the raw, unconverted value + */ + public String getValue() + { + return value; + } +} diff --git a/source/java/org/alfresco/repo/dictionary/M2Property.java b/source/java/org/alfresco/repo/dictionary/M2Property.java index 55db4978ba..e2435ccb2a 100644 --- a/source/java/org/alfresco/repo/dictionary/M2Property.java +++ b/source/java/org/alfresco/repo/dictionary/M2Property.java @@ -16,6 +16,9 @@ */ package org.alfresco.repo.dictionary; +import java.util.Collections; +import java.util.List; + /** * Property Definition @@ -37,7 +40,7 @@ public class M2Property private boolean isIndexedAtomically = true; private boolean isStoredInIndex = false; private boolean isTokenisedInIndex = true; - + private List constraints; /*package*/ M2Property() { @@ -192,5 +195,17 @@ public class M2Property { this.isTokenisedInIndex = isTokenisedInIndex; } - + + + public List getConstraints() + { + if (constraints == null) + { + return Collections.emptyList(); + } + else + { + return constraints; + } + } } diff --git a/source/java/org/alfresco/repo/dictionary/M2PropertyDefinition.java b/source/java/org/alfresco/repo/dictionary/M2PropertyDefinition.java index d6f2f713aa..8f8e10a73f 100644 --- a/source/java/org/alfresco/repo/dictionary/M2PropertyDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2PropertyDefinition.java @@ -16,11 +16,18 @@ */ package org.alfresco.repo.dictionary; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.ModelDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.QName; @@ -33,49 +40,85 @@ import org.alfresco.service.namespace.QName; /*package*/ class M2PropertyDefinition implements PropertyDefinition { private ClassDefinition classDef; - private M2Property property; + private M2Property m2Property; private QName name; private QName propertyTypeName; private DataTypeDefinition dataType; + private List constraints = new ArrayList(5); + private Map constraintsByQName = new HashMap(7); - - /*package*/ M2PropertyDefinition(ClassDefinition classDef, M2Property m2Property, NamespacePrefixResolver resolver) + /*package*/ M2PropertyDefinition( + ClassDefinition classDef, + M2Property m2Property, + NamespacePrefixResolver prefixResolver) { this.classDef = classDef; - this.property = m2Property; + this.m2Property = m2Property; // Resolve Names - this.name = QName.createQName(property.getName(), resolver); - this.propertyTypeName = QName.createQName(property.getType(), resolver); + this.name = QName.createQName(m2Property.getName(), prefixResolver); + this.propertyTypeName = QName.createQName(m2Property.getType(), prefixResolver); } - /*package*/ M2PropertyDefinition(ClassDefinition classDef, PropertyDefinition propertyDef, M2PropertyOverride override) + /*package*/ M2PropertyDefinition( + ClassDefinition classDef, + PropertyDefinition propertyDef, + M2PropertyOverride override) { this.classDef = classDef; - this.property = createOverriddenProperty(propertyDef, override); + this.m2Property = createOverriddenProperty(propertyDef, override); this.name = propertyDef.getName(); this.dataType = propertyDef.getDataType(); this.propertyTypeName = this.dataType.getName(); } - /*package*/ void resolveDependencies(ModelQuery query) + /*package*/ void resolveDependencies( + ModelQuery query, + NamespacePrefixResolver prefixResolver, + Map modelConstraints) { if (propertyTypeName == null) { - throw new DictionaryException("Property type of property " + name.toPrefixString() + " must be specified"); + throw new DictionaryException( + "d_dictionary.property.err.property_type_not_specified", + name.toPrefixString()); } dataType = query.getDataType(propertyTypeName); if (dataType == null) { - throw new DictionaryException("Property type " + propertyTypeName.toPrefixString() + " of property " + name.toPrefixString() + " is not found"); + throw new DictionaryException( + "d_dictionary.property.err.property_type_not_found", + propertyTypeName.toPrefixString(), name.toPrefixString()); } // ensure content properties are not multi-valued if (propertyTypeName.equals(DataTypeDefinition.CONTENT) && isMultiValued()) { - throw new DictionaryException("Content properties must be single-valued"); + throw new DictionaryException("d_dictionary.property.err.single_valued_content"); + } + + // Construct constraints + for (M2Constraint constraint : m2Property.getConstraints()) + { + ConstraintDefinition def = new M2ConstraintDefinition(this, constraint, prefixResolver); + QName qname = def.getName(); + if (constraintsByQName.containsKey(qname)) + { + throw new DictionaryException( + "d_dictionary.property.err.duplicate_constraint_on_property", + def.getName().toPrefixString(), name.toPrefixString()); + } + else if (modelConstraints.containsKey(qname)) + { + throw new DictionaryException( + "d_dictionary.model.err.duplicate_constraint_on_model", + def.getName().toPrefixString()); + } + constraintsByQName.put(qname, def); + constraints.add(def); + modelConstraints.put(qname, def); } } @@ -153,7 +196,7 @@ import org.alfresco.service.namespace.QName; String value = M2Label.getLabel(classDef.getModel(), "property", name, "title"); if (value == null) { - value = property.getTitle(); + value = m2Property.getTitle(); } return value; } @@ -167,7 +210,7 @@ import org.alfresco.service.namespace.QName; String value = M2Label.getLabel(classDef.getModel(), "property", name, "description"); if (value == null) { - value = property.getDescription(); + value = m2Property.getDescription(); } return value; } @@ -178,7 +221,7 @@ import org.alfresco.service.namespace.QName; */ public String getDefaultValue() { - return property.getDefaultValue(); + return m2Property.getDefaultValue(); } @@ -205,7 +248,7 @@ import org.alfresco.service.namespace.QName; */ public boolean isMultiValued() { - return property.isMultiValued(); + return m2Property.isMultiValued(); } @@ -214,7 +257,7 @@ import org.alfresco.service.namespace.QName; */ public boolean isMandatory() { - return property.isMandatory(); + return m2Property.isMandatory(); } @@ -223,7 +266,7 @@ import org.alfresco.service.namespace.QName; */ public boolean isProtected() { - return property.isProtected(); + return m2Property.isProtected(); } @@ -232,7 +275,7 @@ import org.alfresco.service.namespace.QName; */ public boolean isIndexed() { - return property.isIndexed(); + return m2Property.isIndexed(); } @@ -241,7 +284,7 @@ import org.alfresco.service.namespace.QName; */ public boolean isStoredInIndex() { - return property.isStoredInIndex(); + return m2Property.isStoredInIndex(); } @@ -250,7 +293,7 @@ import org.alfresco.service.namespace.QName; */ public boolean isIndexedAtomically() { - return property.isIndexedAtomically(); + return m2Property.isIndexedAtomically(); } @@ -259,7 +302,11 @@ import org.alfresco.service.namespace.QName; */ public boolean isTokenisedInIndex() { - return property.isTokenisedInIndex(); + return m2Property.isTokenisedInIndex(); + } + + public List getConstraints() + { + return Collections.unmodifiableList(constraints); } - } diff --git a/source/java/org/alfresco/repo/dictionary/ModelQuery.java b/source/java/org/alfresco/repo/dictionary/ModelQuery.java index 5c8414d624..a1591b32a3 100644 --- a/source/java/org/alfresco/repo/dictionary/ModelQuery.java +++ b/source/java/org/alfresco/repo/dictionary/ModelQuery.java @@ -20,6 +20,7 @@ import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.namespace.QName; @@ -81,6 +82,14 @@ import org.alfresco.service.namespace.QName; */ public PropertyDefinition getProperty(QName name); + /** + * Gets the specified property constraint + * + * @param name the qualified name of the property constraint + * @return + */ + public ConstraintDefinition getConstraint(QName name); + /** * Gets the specified association * diff --git a/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java b/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java new file mode 100644 index 0000000000..928aca5f8c --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/constraint/AbstractConstraint.java @@ -0,0 +1,88 @@ +/* + * 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.Collection; + +import org.alfresco.service.cmr.dictionary.Constraint; +import org.alfresco.service.cmr.dictionary.DictionaryException; + +/** + * Base services for constraints. + * + * @author Derek Hulley + */ +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"; + + /** + * @see #evaluateSingleValue(Object) + * @see #evaluateCollection(Collection) + */ + @SuppressWarnings("unchecked") + public final void evaluate(Object value) + { + try + { + // check for collection + if (value instanceof Collection) + { + Collection collection = (Collection) value; + evaluateCollection(collection); + } + else + { + evaluateSingleValue(value); + } + } + catch (DictionaryException e) + { + // this can go + throw e; + } + catch (Throwable e) + { + throw new DictionaryException(AbstractConstraint.ERR_EVALUATE_EXCEPTION, this, e.getMessage()); + } + } + + /** + * Only override if there is some specific evaluation that needs to be performed on the + * collection as a whole. + * + * @param collection the collection of values to evaluate + * + * @see #evaluateSingleValue(Object) + */ + protected void evaluateCollection(Collection collection) + { + for (Object value : collection) + { + evaluateSingleValue(value); + } + } + + /** + * Support for evaluation of properties. The value passed in will never be a + * collection. + * + * @throws DictionaryException 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 new file mode 100644 index 0000000000..652eb92ecd --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/constraint/ConstraintsTest.java @@ -0,0 +1,142 @@ +/* + * 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.List; + +import junit.framework.TestCase; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.dictionary.DictionaryDAOTest; +import org.alfresco.service.cmr.dictionary.DictionaryException; + +/** + * @see org.alfresco.service.cmr.dictionary.Constraint + * @see org.alfresco.repo.dictionary.constraint.AbstractConstraint + * @see org.alfresco.repo.dictionary.constraint.RegexConstraint + * + * @author Derek Hulley + */ +public class ConstraintsTest extends TestCase +{ + @Override + protected void setUp() throws Exception + { + // register resource bundles for messages + I18NUtil.registerResourceBundle(DictionaryDAOTest.TEST_RESOURCE_MESSAGES); + } + + /** + * ensure that the default handling of checks on collections will work + */ + public void testCollections() throws Exception + { + DummyConstraint constraint = new DummyConstraint(); + constraint.initialize(); + + List dummyObjects = new ArrayList(3); + dummyObjects.add("ABC"); // correct + dummyObjects.add("DEF"); // correct + dummyObjects.add(this); // NO + try + { + constraint.evaluate(dummyObjects); + fail("Failed to detected constraint violation in collection"); + } + catch (DictionaryException e) + { + // expected + } + // check that the two strings were properly dealt with + assertEquals("String values not checked", 2, constraint.tested.size()); + } + + public void testRegexConstraint() throws Exception + { + RegexConstraint constraint = new RegexConstraint(); + constraint.setExpression("[A-Z]*"); + constraint.initialize(); + + // do some successful stuff + constraint.evaluate("ABC"); + constraint.evaluate("DEF"); + + // now some failures + try + { + constraint.evaluate("abc"); + fail("Regular expression evaluation should have failed: abc"); + } + catch (DictionaryException e) + { + String msg = e.getMessage(); + assertFalse("I18N of constraint message failed", msg.startsWith("d_dictionary.constraint")); + } + + // 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 (DictionaryException e) + { + } + } + + private enum DummyEnum + { + ABC, + DEF, + abc; + } + + private class DummyConstraint extends AbstractConstraint + { + private List tested; + + public void initialize() + { + tested = new ArrayList(4); + } + + /** + * Fails on everything but String values, which pass. + * Null values cause runtime exceptions and all other failures are by + * DictionaryException. + */ + @Override + protected void evaluateSingleValue(Object value) + { + if (value == null) + { + throw new NullPointerException("Null value in dummy test"); + } + else if (value instanceof String) + { + tested.add(value); + } + else + { + throw new DictionaryException("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 new file mode 100644 index 0000000000..d7117b1a64 --- /dev/null +++ b/source/java/org/alfresco/repo/dictionary/constraint/RegexConstraint.java @@ -0,0 +1,66 @@ +/* + * 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.DictionaryException; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; + +/** + * Constraint implementation that performs regular expression comparisons. + * 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. + * + * @see java.lang.String#matches(java.lang.String) + * + * @author Derek Hulley + */ +public class RegexConstraint extends AbstractConstraint +{ + public static final String CONSTRAINT_REGEX_NO_MATCH = "d_dictionary.constraint.regex.no_match"; + + private String expression; + + /** + * Set the regular expression used to evaluate string values + * @param expression similar to the {@link String#matches(java.lang.String) argument + */ + public void setExpression(String expression) + { + this.expression = expression; + } + + public void initialize() + { + if (expression == null) + { + throw new DictionaryException(AbstractConstraint.ERR_PROP_NOT_SET, "expression"); + } + } + + public 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) + { + throw new DictionaryException(RegexConstraint.CONSTRAINT_REGEX_NO_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 d379289328..8365d96ed5 100644 --- a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml +++ b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml @@ -21,6 +21,12 @@ + + + + [A-Z]* + + @@ -34,6 +40,9 @@ d:text true + + + diff --git a/source/java/org/alfresco/repo/dictionary/m2binding.xml b/source/java/org/alfresco/repo/dictionary/m2binding.xml index e9f73379d0..90f0d4d8ff 100644 --- a/source/java/org/alfresco/repo/dictionary/m2binding.xml +++ b/source/java/org/alfresco/repo/dictionary/m2binding.xml @@ -41,6 +41,10 @@ + + + + @@ -102,6 +106,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/service/cmr/dictionary/Constraint.java b/source/java/org/alfresco/service/cmr/dictionary/Constraint.java new file mode 100644 index 0000000000..413423d31d --- /dev/null +++ b/source/java/org/alfresco/service/cmr/dictionary/Constraint.java @@ -0,0 +1,56 @@ +/* + * 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.service.cmr.dictionary; + + +/** + * The interface for classes that implement constraints on property values. + *

+ * Implementations of the actual constraint code should must not synchronize + * or in any other way block threads. Concurrent access of the evaluation + * method is expected, but will always occur after initialization has completed. + *

+ * Attention to performance is crucial for all implementations as + * instances of this class are heavily used. + *

+ * The constraint implementations can provide standard setter methods that will + * be populated by bean setter injection. Once all the available properties have + * been set, the contraint will be initialized. + * + * @author Derek Hulley + */ +public interface Constraint +{ + /** + * Initializes the constraint with appropriate values, which will depend + * on the implementation itself. This method can be implemented as a + * once-off, i.e. reinitialization does not have to be supported. + * + * @param parameters constraint parameters + */ + public void initialize(); + + /** + * Evaluates a property value according to the implementation and initialization + * parameters provided. + * + * @param value the property value to check + * + * @throws ConstraintException if the value doesn't pass all constraints + */ + public void evaluate(Object value); +} diff --git a/source/java/org/alfresco/service/cmr/dictionary/ConstraintDefinition.java b/source/java/org/alfresco/service/cmr/dictionary/ConstraintDefinition.java new file mode 100644 index 0000000000..b4cf79aba7 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/dictionary/ConstraintDefinition.java @@ -0,0 +1,42 @@ +/* + * 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.service.cmr.dictionary; + +import org.alfresco.service.namespace.QName; + +/** + * Property constraint definition + * + * @author Derek Hulley + */ +public interface ConstraintDefinition +{ + /** + * @return defining model + */ + public ModelDefinition getModel(); + + /** + * @return Returns the qualified name of the constraint + */ + public QName getName(); + + /** + * @return Returns the constraint implementation + */ + public Constraint getConstraint(); +} diff --git a/source/java/org/alfresco/service/cmr/dictionary/ConstraintException.java b/source/java/org/alfresco/service/cmr/dictionary/ConstraintException.java new file mode 100644 index 0000000000..1a84ba2d5a --- /dev/null +++ b/source/java/org/alfresco/service/cmr/dictionary/ConstraintException.java @@ -0,0 +1,34 @@ +/* + * 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.service.cmr.dictionary; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Thrown when property value fails to meet a property constraint. + * + * @author Derek Hulley + */ +public class ConstraintException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = -3925105163386197586L; + + public ConstraintException(String msgId, Object ... args) + { + super(msgId, args); + } +} diff --git a/source/java/org/alfresco/service/cmr/dictionary/DictionaryException.java b/source/java/org/alfresco/service/cmr/dictionary/DictionaryException.java index fb62d706cd..b9d3c6dbbf 100644 --- a/source/java/org/alfresco/service/cmr/dictionary/DictionaryException.java +++ b/source/java/org/alfresco/service/cmr/dictionary/DictionaryException.java @@ -16,13 +16,14 @@ */ package org.alfresco.service.cmr.dictionary; +import org.alfresco.error.AlfrescoRuntimeException; /** * Base Exception of Data Dictionary Exceptions. * * @author David Caruana */ -public class DictionaryException extends RuntimeException +public class DictionaryException extends AlfrescoRuntimeException { private static final long serialVersionUID = 3257008761007847733L; @@ -36,4 +37,13 @@ public class DictionaryException extends RuntimeException super(msg, cause); } + public DictionaryException(String msgId, Object ... args) + { + super(msgId, args); + } + + public DictionaryException(String msgId, Throwable cause, Object ... args) + { + super(msgId, args, cause); + } } diff --git a/source/java/org/alfresco/service/cmr/dictionary/PropertyDefinition.java b/source/java/org/alfresco/service/cmr/dictionary/PropertyDefinition.java index af519bf2f8..4b38993c6f 100644 --- a/source/java/org/alfresco/service/cmr/dictionary/PropertyDefinition.java +++ b/source/java/org/alfresco/service/cmr/dictionary/PropertyDefinition.java @@ -16,6 +16,8 @@ */ package org.alfresco.service.cmr.dictionary; +import java.util.List; + import org.alfresco.service.namespace.QName; /** @@ -98,4 +100,10 @@ public interface PropertyDefinition */ public boolean isIndexedAtomically(); + /** + * Get all constraints that apply to the property value + * + * @return Returns a list of property constraint definitions + */ + public List getConstraints(); }