This page explains how to add validation constraints to a document type by using the Nuxeo component org.nuxeo.ecm.core.api.DocumentValidationService
.
Document validation is a feature which:
- Lets you express constraints on Document's fields: field type, format, content, ...
- Provides the APIs to validate documents
- Reports the set of violations (localized)
- Provides the APIs to get constraints on document fields
- Lets you express that some fields are references to other documents, directory entries, Nuxeo users or Nuxeo groups
- Provides the APIs to fetch objects referenced by a document field
- Lets you define references to your own business objects
For now, document validation does not:
- Manage cross field validation
- Manage list field size
- Manage existing data cleaning
Concepts
Constraints are defined in schemas, on any field. It is based on XML Schema facets (W3C - XML Schema - Datatypes). But Nuxeo does not implement the whole XML Schema specification and Nuxeo adds non-XSD features.
Constraints are loaded by Nuxeo Core as Java objects implementing org.nuxeo.ecm.core.schema.types.constraints.Constraint
.
public interface Constraint extends Serializable {
boolean validate(Object object);
String getErrorMessage(Object invalidValue, Locale locale);
Description getDescription();
class Description {
public String getName() {...}
public Map<String, Serializable> getParameters() {...}
}
}
Constraints on a document field are available through the API by calling org.nuxeo.ecm.core.schema.types.Field.getConstraints()
.
Constraints on a field type are available through the API by calling org.nuxeo.ecm.core.schema.types.SimpleType.getConstraints()
. Please note that constraints on fields integrate the constraints of the field type.
Documents, document properties or simple values can be validated through Nuxeo service org.nuxeo.ecm.core.api.validation.DocumentValidationService
.
public interface DocumentValidationService {
ConstraintViolationReport validate(DocumentModel document);
ConstraintViolationReport validate(Property property);
ConstraintViolationReport validate(Field field, Object value);
...
}
Validation can be enabled/disabled at different level by means of the extension point activations
of the org.nuxeo.ecm.core.api.DocumentValidationService
component.
Setting up Constraints
Below are the properties of the different constraints that you can set on schema fields.
Text and Number Format
Name: PatternConstraint
Parameters:
Pattern
: the regular expression which constrains the format
Availability: Any XSD simple type derived from datatype xsd:string
, xsd:integer
, xsd:long
or xsd:decimal
(or alternatives like xsd:negativeinteger
).
Behavior: This constraint ensures that a field matches a regular expression. null
value is always valid (use the NotNullConstraint
in combination).
Examples:
An email
<xs:element name="email"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:pattern value="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?*" /> </xs:restriction> </xs:simpleType> </xs:element>
US Zip Code + 4 digit extension Postal Code
<xs:simpleType name="zipcodetype"> <xs:restriction base="xs:integer"> <xs:pattern value="[0-9]{5}([- /]?[0-9]{4})?" /> </xs:restriction> </xs:simpleType> <xs:element name="zipcode" type="zipcodetype" />
List of Values
Name: EnumConstraint
Parameters:
Values
: A list of possible value for the field
Availability: Any XSD simple type derived from datatype xsd:string
, xsd:integer
, xsd:long
or xsd:decimal
(or alternatives like xsd:negativeinteger
).
Behavior: This constraint ensures that the value of a field belongs to a set of values. null
value is always valid (use the NotNullConstraint
in combination).
Example: Preference for use of a hand
<xs:element name="mainColor">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="red" />
<xs:enumeration value="green" />
<xs:enumeration value="blue" />
<xs:enumeration value="black" />
<xs:enumeration value="white" />
</xs:restriction>
</xs:simpleType>
</xs:element>
Length of Text
Name: LengthConstraint
Parameters:
Minimum
: The minimum number of characters the field can contain, if specified.Maximum
: The maximum number of characters the field can contain, if specified.
Availability: Any XSD simple type derived from datatype xsd:string
(or alternatives like xsd:name
).
Behavior: This constraint ensures that a field contains a given quantity of characters. null
value is always valid (use the NotNullConstraint
in combination).
LengthConstraint
may alter the database schema when using an SQL backend. If the minLenght
is defined, a varchar with the given length will be created.
Examples:
A login with a minimal size of 4 characters
<xs:element name="login"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:minLength value="4" /> </xs:restriction> </xs:simpleType> </xs:element>
A credit card code with exactly 15 characters
<xs:element name="code"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:minLength value="15" /> <xs:maxLength value="15" /> </xs:restriction> </xs:simpleType> </xs:element>
Numeric Range
Name: NumericIntervalConstraint
Parameters:
Minimum
: The lower bound, if specified.MinimumInclusive
:true
if the minimum bound is included,false
otherwise.Maximum
: The upper bound, if specified.MaximumInclusive
:true
if maximum bound is included,false
otherwise.
Availability: Any XSD simple type derived from datatype xsd:integer
, xsd:long
or xsd:decimal
(or alternatives like xsd:negativeinteger
).
Behavior: This constraint ensures that a number is in a given range, null
value is always valid (use the NotNullConstraint
in combination).
Examples:
A month (Western calendar)
<xs:element name="month"> <xs:simpleType> <xs:restriction base="xs:integer"> <xs:minInclusive value="1" /> <xs:maxInclusive value="12" /> </xs:restriction> </xs:simpleType> </xs:element>
The temperature in the universe (Fahrenheit degrees - above absolute zero)
<xs:element name="temperature"> <xs:simpleType> <xs:restriction base="xs:integer"> <xs:minExclusive value="-459.67" /> </xs:restriction> </xs:simpleType> </xs:element>
Time Range
Name: DateIntervalConstraint
Parameters:
Minimum
: The start date of the time range, if specified.MinimumInclusive
:true
if the start date is included,false
otherwise.Maximum
: The end date of the time range, if specified.MaximumInclusive
:true
if end date is included,false
otherwise.
Availability: Any XSD simple type derived from datatype xsd:date
(or alternatives like xsd:dateTime
).
Behavior: This constraint ensures that a date is in a given time range. null
value is always valid (use the NotNullConstraint
in combination).
Example: Any date during 21 century
<xs:element name="event21century">
<xs:simpleType>
<xs:restriction base="xs:date">
<xs:minInclusive value="2001-01-01" />
<xs:maxInclusive value="2100-12-31" />
</xs:restriction>
</xs:simpleType>
</xs:element>
Mandatory Field
Name: NotNullConstraint
Parameters: none
Availability: Any document's field.
Behavior: This constraint ensures that a document's field has a value.
The NotNullConstraint
Is the only constraint defined on the <xsd:element>
. Other constraints are defined under <xsd:simpleType>
. This constraint is also accessible through Field.getConstraints()
but not through SimpleType.getConstraints()
.
Unlike XML Schema, Nuxeo's fields are optional if nothing was specified. To manage this difference, you have to write the nillable
attribute from the XSD norm plus another specific to Nuxeo.
The non-standard nillable
attribute must belong to a specific XML namespace: http://www.nuxeo.org/ecm/schemas/core/validation/
. Without this namespace, the field will not be mandatory.
Example: A mandatory description
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:nxsv="http://www.nuxeo.org/ecm/schemas/core/validation/"
xmlns:nxs="http://www.nuxeo.org/ecm/schemas/example"
targetNamespace="http://www.nuxeo.org/ecm/schemas/example">
<xs:element name="description" type="xs:string" nillable="false" nxsv:nillable="false" />
</xs:schema>
Constraint Based on the Type
This constraint is automatically added according to XSD datatype on which the type of the document's field is based.
Names: string
, long
, integer
, double
, date
, boolean
and binary
Parameters: none
Behavior: This constraint controls the Java types supported by the field. It depends on the XSD datatype on which the element is based.null
value is always valid (use the NotNullConstraint
in combination).
Constraint name | Supported Java types |
---|---|
string | any |
long | any java.lang.Number |
integer | any java.lang.Number |
double | any java.lang.Number |
date | any java.util.Date or java.util.Calendar |
boolean | java.lang.Boolean |
binary | _-_ |
Setting up References
Below are the properties of the different references you can set on schema fields.
Reference to Another Document
Name: documentResolver
Parameters:
store
:id
orpath
. Ifid
, a document's id is expected. Ifpath
, a document's place is expected.
Expected value: a concatenation of repository name and id (ex: default:45678-5678-45678-2346
) or repository name and path (ex: default:/domain/places/doc
).
Availability: Any XSD simple type derived from datatype xsd:string
(or alternatives like xsd:name
).
Behavior: This constraint ensures that a referenced document exists. null
value is always valid (use the NotNullConstraint
in combination).
Please note that the namespace http://www.nuxeo.org/ecm/schemas/core/external-references/
is necessary to define a resolver and its parameters.
Examples:
A reference to another document in a field named "favorite" (note the attribute
ref:store="id"
, the reference will follow the document moves)<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ref="http://www.nuxeo.org/ecm/schemas/core/external-references/" xmlns:nxs="http://www.nuxeo.org/ecm/schemas/example" targetNamespace="http://www.nuxeo.org/ecm/schemas/example"> <xs:element name="favorite"> <xs:simpleType> <xs:restriction base="xs:string" ref:resolver="documentResolver" ref:store="id" /> </xs:simpleType> </xs:element> </xs:schema>
A reference to another document in a field named "workspace" (note the attribute
ref:store="path"
, the reference will not follow the document moves)<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ref="http://www.nuxeo.org/ecm/schemas/core/external-references/" xmlns:nxs="http://www.nuxeo.org/ecm/schemas/example" targetNamespace="http://www.nuxeo.org/ecm/schemas/example"> <xs:element name="workspace"> <xs:simpleType> <xs:restriction base="xs:string" ref:resolver="documentResolver" ref:store="path" /> </xs:simpleType> </xs:element> </xs:schema>
Reference to a Nuxeo User or a Nuxeo Group
Name: userManagerResolver
Parameters:
includeUsers
: true if the constraint search for user, false otherwise.includeGroups
: true if the constraint search for group, false otherwise.
Expected value: a user name prefixed by "user:"
(user:Administrator
) or a group name prefixed by group:
(group:members
).
Availability: Any XSD simple type derived from datatype xsd:string
(or alternatives like xsd:name
).
Behavior: This constraint ensures that a referenced user or group exists. null
value is always valid (use the NotNullConstraint
in combination).
Please note that the namespace http://www.nuxeo.org/ecm/schemas/core/external-references/
is necessary to define a resolver and its parameters.
Examples:
A reference to an author which must be a Nuxeo user (the
ref:type="user"
indicates the field must contain a user - not a group).<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ref="http://www.nuxeo.org/ecm/schemas/core/external-references/" xmlns:nxs="http://www.nuxeo.org/ecm/schemas/example" targetNamespace="http://www.nuxeo.org/ecm/schemas/example"> <xs:element name="author"> <xs:simpleType> <xs:restriction base="xs:string" ref:resolver="userManagerResolver" ref:type="user" /> </xs:simpleType> </xs:element> </xs:schema>
A reference to a Nuxeo group (the
ref:type="group"
indicates the field must contain a group - not a user).<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ref="http://www.nuxeo.org/ecm/schemas/core/external-references/" xmlns:nxs="http://www.nuxeo.org/ecm/schemas/example" targetNamespace="http://www.nuxeo.org/ecm/schemas/example"> <xs:element name="group"> <xs:simpleType> <xs:restriction base="xs:string" ref:resolver="userManagerResolver" ref:type="group" /> </xs:simpleType> </xs:element> </xs:schema>
A reference to a set of Nuxeo user or Nuxeo group (here, there is no
ref:type
attribute - the field accept user and group).<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ref="http://www.nuxeo.org/ecm/schemas/core/external-references/" xmlns:nxs="http://www.nuxeo.org/ecm/schemas/example" targetNamespace="http://www.nuxeo.org/ecm/schemas/example"> <xs:element name="access"> <xs:complexType> <xs:sequence> <xs:element name="principal" minOccurs="0" maxOccurs="unbounded"> <xs:simpleType> <xs:restriction base="xs:string" ref:resolver="userManagerResolver" /> </xs:simpleType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
Reference to Nuxeo Directory/Vocabulary Entry
Name: directoryResolver
Parameters:
directory
: the referenced directory.
Expected value: An id of an directory entry.
Availability: Any XSD simple type derived from datatype xsd:string
(or alternatives like xsd:name
).
Behavior: This constraint ensures that a referenced directory entry exists. null
value is always valid (use the NotNullConstraint
in combination).
Please note that the namespace http://www.nuxeo.org/ecm/schemas/core/external-references/
is necessary to define a resolver and its parameters.
Example: A car brand (the ref:directory="carBrands"
indicates the field must refer to an entry of the carBrands
directory).
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ref="http://www.nuxeo.org/ecm/schemas/core/external-references/"
xmlns:nxs="http://www.nuxeo.org/ecm/schemas/example"
targetNamespace="http://www.nuxeo.org/ecm/schemas/example">
<xs:element name="carBrand">
<xs:simpleType>
<xs:restriction base="xs:string" ref:resolver="directoryResolver" ref:directory="carBrands" />
</xs:simpleType>
</xs:element>
</xs:schema>
Document Validation APIs
Explicit Validation
Once you described your data constraints, you can use the validation API to be sure your data are correct.
The validation API provides multiple operations to manage that.
Get the validation service
import org.nuxeo.ecm.core.api.validation.DocumentValidationService;
// ...
DocumentValidationService validator = Framework.getService(DocumentValidationService.class);
Validating a full document
DocumentModel doc = session.createDocumentModel("/", "doc1", "DocumentType");
// update doc ...
DocumentValidationReport report = validator.validate(doc);
Validating the updated fields of a document
DocumentModel doc = session.getDocument(new PathRef("/doc1"));
// update the existing document
DocumentValidationReport report = validator.validate(doc, true); // the second parameter enable "only dirty" validation
Validating a document property
DocumentModel doc = session.getDocument(new PathRef("/doc1"));
// update the existing document
DocumentValidationReport report = validator.validate(doc, true); // the second parameter enable "only dirty" validation
Validating data according to a field definition
SchemaManager schemaManager = Framework.getService(SchemaManager.class);
Schema schema = schemaManager.getSchema("userprofile");
Field field = schema.getField("birthdate");
GregorianCalendar birthdate = new GregorianCalendar(1984, 5, 15);
DocumentValidationReport report = validator.validate(field, birthdate);
Validating data according to a field XPath
GregorianCalendar birthdate = new GregorianCalendar(1984, 5, 15);
DocumentValidationReport report = validator.validate("userprofile:birthdate", birthdate);
Validating data according to a field XPath (complex fields)
This feature allows to validate a value according to the definition of a simple field which is a child of a complex types hierarchy.
Given this schema:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:nxv="http://nuxeo.com/schemas/validationSample"
targetNamespace="http://nuxeo.com/schemas/validationSample"
xmlns:nxsv="http://www.nuxeo.org/ecm/schemas/core/validation/">
<xs:element name="users">
<xs:complexType>
<xs:sequence>
<xs:element name="user" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="firstname" nillable="false" nxsv:nillable="false">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value=".*\S.*" />
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
<xs:attribute name="lastname" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[A-Z][a-z '-]+" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
You can do:
String firtname = "John";
DocumentValidationReport report = validator.validate("schemprefix:users:user:firstname", firstname);
Automated Validation
Validation can be enable/disable at different level by contributing to the validation service:
<extension target="org.nuxeo.ecm.core.api.DocumentValidationService" point="activations">
<validation context="createDocument" activated="true" />
<validation context="saveDocument" activated="true" />
<validation context="importDocument" activated="true" />
</extension>
By default, the validation is fully activated (create, save, import).
If activated, validation could throw DocumentValidationException
while calling:
CoreSession.createDocument(DocumentModel);
CoreSession.createDocument(DocumentModel[]);
CoreSession.saveDocument(DocumentModel);
CoreSession.saveDocuments(DocumentModel[]);
CoreSession.importDocuments(List<DocumentModel>);
Analysing Validation Reports
DocumentValidationService
's methods return DocumentValidationReport
.
When activated, validation will throw a DocumentValidationException
when creating, saving or importing invalid documents. This exception contains a DocumentValidationReport
. You can get it by calling DocumentValidationException.getReport()
.
This report contains all the ConstraintViolation
met while validating a Document.
You can get a localized error message for each violation by calling ConstraintViolation.getMessage(Locale)
.
You can also get each violation, the broken constraint, the corresponding invalid value and the place where the violation append.
DocumentValidationReport report = validator.validate(document);
// document constains a list of users with lastname/firtname (lastname and firstname should not be null and must contains non-space characters.
if (report.hasError()) {
System.out.println(String.format("%s errors found :", report.numberOfErrors()));
for (ConstraintViolation violation : report.asList()) {
System.out.println(String.format("- Broken constraint %s", violation.getConstraint().getDescription()));
System.out.println(String.format(" invalid value : \"%s\"", violation.getInvalidValue()));
System.out.print(" location: ");
for (PathNode node : violation.getPath()) {
System.out.print(node.getField().getName());
if (node.isListItem()) {
System.out.print("[" + node.getIndex() + "]");
}
System.out.print(":");
}
System.out.println();
System.out.println();
}
}
// output:
3 errors found :
- Broken constraint NotNullConstraint{}
invalid value : "null"
location: vs:users:user[0]:lastname:
- Broken constraint PatternConstraint{Pattern=.*\S.*}
invalid value : " "
location: vs:users:user[0]:firstname:
- Broken constraint NotNullConstraint{}
invalid value : "null"
location: vs:users:user[1]:firstname:
Playing with Constraints
Through the APIs, you can get the constraints defined for each fields using:
org.nuxeo.ecm.core.schema.types.Type.getConstraints()
: Will return all constraints defined on type (all exceptNotNullConstraint
)org.nuxeo.ecm.core.schema.types.Field.getConstraints()
: Will return all constraint includingNotNullConstraint
(only field can be defined asnillable
)
Example: a constraint introspection
for (Schema schema : doc.getDocumentType().getSchemas()) {
for (Field field : schema.getFields()) {
constraintIntrospection(field, Arrays.asList(field.getName().getPrefix()));
}
}
private void constraintIntrospection(Field field, List<String> path) {
List<String> newPath = new ArrayList<String>(path);
newPath.add(field.getName().getLocalName());
System.out.println(StringUtils.join(newPath, ':') + " has constraints: " + field.getConstraints());
if (field.getType().isListType()) {
Field listField = ((ListType) field.getType()).getField();
constraintIntrospection(listField, newPath);
} else if (field.getType().isComplexType()) {
for (Field childField : ((ComplexType) field.getType()).getFields()) {
constraintIntrospection(childField, newPath);
}
}
}
// output:
vs:users has constraints: []
vs:users:user has constraints: []
vs:users:user:lastname has constraints: [PatternConstraint{Pattern=[A-Z][a-z '-]+}, NotNullConstraint{}, string{}]
vs:users:user:firstname has constraints: [NotNullConstraint{}, PatternConstraint{Pattern=.*\S.*}, string{}]
Fetching Referenced Entities
When a field is defined as a reference and is associated to a resolver, you can use an ObjectResolver
to manage the relation between the field and the corresponding object.
Example, a reference to another document:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ref="http://www.nuxeo.org/ecm/schemas/core/external-references/"
xmlns:nxs="http://www.nuxeo.org/ecm/schemas/example"
targetNamespace="http://www.nuxeo.org/ecm/schemas/example">
<xs:element name="favorite">
<xs:simpleType>
<xs:restriction base="xs:string" ref:resolver="documentResolver" ref:store="id" />
</xs:simpleType>
</xs:element>
</xs:schema>
To know if a field is a reference:
SchemaManager schemaManager = Framework.getService(SchemaManager.class);
Schema schema = schemaManager.getSchema("schema");
Field field = schema.getField("favorite");
if (field.getType().getObjectResolver() != null) {
System.out.println("it's a reference to an object");
}
To know if a property is a reference:
DocumentModel document = ...;
if (document.getObjectResolver("schema:favorite") != null) {
System.out.println("it's a reference to an object");
}
// or
Property property = document.getProperty("schema:favorite");
if (property.getObjectResolver() != null) {
System.out.println("it's a reference to an object");
}
// or finally through the type
if (property.getType().getObjectResolver() != null) {
System.out.println("it's a reference to an object");
}
To get the referenced value:
DocumentModel referenced = document.getObjectResolver("schema:favorite").fetch(DocumentModel.class);
// or
DocumentModel referenced = document.getProperty("schema:favorite").getObjectResolver().fetch(DocumentModel.class);
// both of the 2 previous approach are shortcut for:
Serializable reference = document.getPropertyValue("schema:favorite");
ObjectResolver resolver = document.getProperty("schema:favorite").getType().getObjectResolver();
DocumentModel referenced = resolver.fetch(DocumentModel.class, reference);
To set the referenced document:
DocumentModel referenced = ...;
document.getObjectResolver("schema:favorite").setObject(referenced);
// or
document.getProperty("schema:favorite").getObjectResolver().setObject(referenced);
// both of the 2 previous approach are shortcut for:
ObjectResolver resolver = document.getProperty("schema:favorite").getType().getObjectResolver();
Serializable reference = resolver.getReference(referenced);
document.setPropertyValue("schema:favorite", reference);