One of the important features of JCR is that it allows your applications to define and use custom node types, which can be used for either primary types or mixin types. This section talks about how to define and use the node types, property definitions, and child node definitions.
Compact Node Definition (CND)
The JCR 2.0 specification defines a file format called "Compact Node Definition", or CND. True to its namesake, the format does indeed make it possible to define node types in a very compact form. It supports Java-style comments, uses JCR-style namespace prefixes, and does not require whitespace or newlines around key characters (e.g., '[', ']', '>', ',', '(', ')', '=', and '<').
This documentation is a summary of the CND format. For the true specification of the format, see Section 25.2 in the JCR 2.0 specification.
Here is a small CND file that defines just a few of the 33 built-in node types, and hopefully gives you an example of what CND files look like:
<jcr='http://www.jcp.org/jcr/1.0'>
<nt='http://www.jcp.org/jcr/nt/1.0'>
<mix='http://www.jcp.org/jcr/mix/1.0'>
/* Every node type directly or indirectly extends from 'nt:base' */
[nt:base] abstract
- jcr:primaryType (name) mandatory autocreated protected compute
- jcr:mixinTypes (name) protected multiple compute
[nt:unstructured] orderable
- * (undefined) multiple
- * (undefined)
+ * (nt:base) = nt:unstructured sns version
[mix:created] mixin
- jcr:created (date) protected
- jcr:createdBy (string) protected
[nt:hierarchyNode] > mix:created abstract
// The 'nt:file' and 'nt:folder' node types allows applications to store
// files and directories as content inside a repository
[nt:file] > nt:hierarchyNode
+ jcr:content (nt:base) primary mandatory
[nt:folder] > nt:hierarchyNode
+ * (nt:hierarchyNode) version
[mix:referenceable] mixin
- jcr:uuid (string) mandatory autocreated protected initialize
[mix:mimeType] mixin
- jcr:mimeType (string)
- jcr:encoding (string)
[mix:lastModified] mixin
- jcr:lastModified (date)
- jcr:lastModifiedBy (string)
[nt:resource] > mix:mimeType, mix:lastModified
- jcr:data (binary) primary mandatory
Let's break apart the format into the different parts.
Declaring namespaces
A namespace is declared using the form:
< prefix = uri >
where prefix is the quoted or unquoted string literal used in the rest of the file as the prefix for the namespace, and uri is the quoted URI for the namespace. For readability, most people place each namespace declaration on a separate line near the top of the file, but that's not required.
Declaring node types
Each node type declaration is made up of several parts. The first is the specification of the node type's name and attributes, and is of the form:
[ prefixedName ] > supertypes attributes
where prefixedName is the name of the node type in prefixed notation (e.g., "nt:base", "nt:unstructured", "mix:lastModified", etc), supertypes is the optional comma-separated list of the prefixed names of the node type's supertypes, and attributes is the optional list of node type attributes:
Attribute Keywords
|
Description
|
orderable ord o
|
The node type supports orderable children. If absent, then orderable children are not supported.
|
mixin mix m
|
The node type is a mixin. If absent, the node type can be used as a primary type.
|
abstract abs a
|
The node type is abstract and cannot be directly used as nodes' primary type or mixin type. If absent, the node type is concrete.
|
noquery nq query q
|
Specifies whether the node type can be queried. If 'noquery' or 'nq' are used, then the node type cannot be queried; if 'query' or 'q' are used, then the node type can be queried. If neither are specified, ModeShape assumes that the node type can be queried. If multiple are specified, the last one is used.
|
primaryitem !
|
The string following this keyword specifies the prefixed name of the property or child (including same-name-sibling index if required) that will be considered the primary item , which is the child that can be navigated using a special method and allows a client to more easily navigate an unknown structure. If absent, the node type does not have a primary item.
|
After the attributes are listed the property definitions and child node definitions.
Property definitions
Each property definition begins with a '-' character and follows the form:
- prefixedName ( type ) = defaultValues attributes
where
-
prefixedName is the name of the property definition in prefix form
-
type is the case-insensitive name of the JCR property type, and is one of: STRING, BINARY, LONG, DOUBLE, DATE, BOOLEAN, NAME, PATH, REFERENCE, WEAKREFERENCE, URI, and DECIMAL
-
defaultValues is an optional comma-separated list of quoted string literals containing the string representation of the default values; multiple are allowed for multi-valued properties
-
attributes is the optional list of property definition attributes:
Attribute Keywords
|
Description
|
mandatory man m
|
The parent node must have at least least one property to which this property definition applies.
|
autocreated aut a
|
The property is automatically created when the node is created with the node type as primary type, or when the node type is added as a mixin to a node. If absent, the property is not auto-created.
|
protected pro p
|
The property to which this definition applies is protected, meaning it can be read but not modified by client applications. When absent, the property can be set by client applications.
|
multiple mul *
|
The property is multi-valued. If absent, the property is single-valued.
|
COPY VERSION INITIALIZE COMPUTE IGNORE ABORT
|
The specification for how the property is to be handled when the node is versioned. When absent, the default versioning is COPY.
|
< constraints
|
The quoted string literals containing regular expressions or exact matches for the values. When constraints are provided, every value must satisfy at least one constraint on the property definition. If absent, there are no constraints on the values.
|
queryops qop
|
This keyword is followed by a quoted string literal containing the comma-separated operators that can be used in property comparison constraints against this property. If absent, all possible operators are used, and this is equivalent to specifying '=, <>, <, <=, >, >=, LIKE'.
|
nofulltext not
|
Specifies whether the property value(s) should be considered when performing a full-text search. If absent, the values will be used in full-text searches.
|
noqueryorder nqord
|
Specifies whether the property can be ordered in queries. If absent, the property can be used to order query results.
|
Child node definitions
Each child node definition begins with a '{+}' character and follows the form:
{+} prefixedName ( requiredTypes ) = defaultType attributes
where
-
prefixedName is the name of the property definition in prefix form
-
requiredTypes is optional comma-separated names of the required node types for the child node. Any child adhering to this child node definition must be instances of at least the required types listed here. If absent, the required type is assumed to be 'nt:base' (of which all nodes are instances).
-
defaultType is an optional name of the node type that should be used by default when creating the child node. If absent, the default type is assumed to be 'nt:unstructured'. The default type can always be overridden by clients using the Node.addNode(String,String) method.
-
attributes is the optional list of child node definition attributes:
Attribute Keywords
|
Description
|
mandatory man m
|
The parent node must have at least least one child to which this child node definition applies.
|
autocreated aut a
|
The child node is automatically created when the node is created with the node type as primary type, or when the node type is added as a mixin to a node. If absent, the child node is not auto-created.
|
protected pro p
|
The child to which this node definition type applies is protected, meaning it can be read but not modified by client applications. When absent, the property can be set by client applications. If absent, the child nodes can be read and removed from the parent node.
|
sns *
|
There may be multiple child nodes with the same name to which this definition applies. Such child nodes will be distinguished with a same-name-sibling index. If absent, same-name-siblings are not allowed.
|
COPY VERSION INITIALIZE COMPUTE IGNORE ABORT
|
The specification for how the child nodes to which this definition applies are to be handled when the parent node is versioned. When absent, the default versioning is COPY.
|
< constraints
|
The quoted string literals containing regular expressions or exact matches for the values. When constraints are provided, every value must satisfy at least one constraint on the property definition. If absent, there are no constraints on the values.
|
queryops qop
|
This keyword is followed by a quoted string literal containing the comma-separated operators that can be used in property comparison constraints against this property. If absent, all possible operators are used, and this is equivalent to specifying '=, <>, <, <=, >, >=, LIKE'.
|
nofulltext not
|
Specifies whether the property value(s) should be considered when performing a full-text search. If absent, the values will be used in full-text searches.
|
noqueryorder nqord
|
Specifies whether the property can be ordered in queries. If absent, the property can be used to order query results.
|
CND example
Consider that we want to define a node type named 'ns:NodeType' that:
Using the CND format where we already defined the 'ns' namespace prefix, we can define this node type as:
[ns:NodeType] > ns:ParentType1, ns:ParentType2 abstract orderable mixin query primaryitem ex:property
- ex:property (STRING) = 'default1', 'default2' mandatory autocreated protected multiple VERSION
< '[.]*\d', 'constraint2' queryops '=, <>, <, <=, >, >=, LIKE' nofulltext noqueryorder
+ ns:node (ns:reqType1, ns:reqType2) = ns:defaultType mandatory autocreated protected sns VERSION
Note that we used the full-length keywords. We could use the mid-length keywords to make it a bit more readable:
[ns:NodeType] > ns:ParentType1, ns:ParentType2 abs ord mix q ! ex:property
- ex:property (STRING) = 'default1', 'default2' man aut pro mul VERSION
< '[.]*\d', 'constraint2' qop '=, <>, <, <=, >, >=, LIKE' nof nqord
+ ns:node (ns:reqType1, ns:reqType2) = ns:defaultType man aut pro sns VERSION
We can make it even more compact by using the shortest keywords:
[ns:NodeType] > ns:ParentType1, ns:ParentType2 a o m q ! ex:property
- ex:property (STRING) = 'default1', 'default2' m a p * VERSION
< '[.]*\d', 'constraint2' qop '=, <>, <, <=, >, >=, LIKE' nof nqord
+ ns:node (ns:reqType1, ns:reqType2) = ns:defaultType m a p * VERSION
Again, this example uses every possible attribute for the node type, the property definition, and the child node definition. Often the default attributes will suffice, making the node definitions even more compact and readable.
Built-in node types
The JCR specification defines 33 node types that are built-in and available for use without applications having to register them. In fact, most JCR implementations will not allow applications to re-register or modify any of the built-in node types. These standard built-in node types and those node types defined in all ModeShape repositories are defined in Built-in node types.
Registering custom node types
While the JCR 2.0 specification uses the CND format within the specification, the only standard API for registering custom node type definitions is to use the programmatic API.
ModeShape provides non-standard a way for clients to register node types by reading a stream containing a CND file. For details, see Registering custom node types.
The standard programmatic API uses mutable template objects that can be created and registered using the javax.jcr.nodetype.NodeTypeManager. Here's some Java code that registers the 'ns:NodeType' node type definition we used earlier:
// Get the node type manager ...
javax.jcr.nodetype.NodeTypeManager mgr = session.getWorkspace().getNodeTypeManager();
// Create a template for the node type ...
NodeTypeTemplate type = mgr.createNodeTypeTemplate();
type.setName("ns:NodeType");
type.setDeclaredSuperTypeNames(new String[]{"ns:ParentType1","ns:ParentType2"});
type.setAbstract(true);
type.setOrderableChildNodes(true);
type.setMixin(true);
type.setQueryable(true);
type.setPrimaryItemName("ex:property");
// Create a template for the property definition ...
PropertyDefinitionTemplate propDefn = mgr.createPropertyDefinitionTemplate();
propDefn.setName("ex:property");
propDefn.setRequiredType(PropertyType.STRING);
ValueFactory valueFactory = session.getValueFactory();
Value[] defaultValues = {valueFactory.createValue("default1"),valueFactory.createValue("default2")};
propDefn.setDefaultValues(defaultValues);
propDefn.setMandatory(true);
propDefn.setAutoCreated(true);
propDefn.setProtected(true);
propDefn.setMultiple(true);
propDefn.setOnParentVersion(OnParentVersionAction.VERSION);
propDefn.setValueConstraints(new String[]{"[.]*\\d","constraint2"});
String[] queryOps = {QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO,
QueryObjectModelConstants.JCR_OPERATOR_NOT_EQUAL_TO,
QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN,
QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO,
QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN,
QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO,
QueryObjectModelConstants.JCR_OPERATOR_LIKE,
};
propDefn.setAvailableQueryOperators(queryOps);
propDefn.setFullTextSearchable(false);
propDefn.setQueryOrderable(false);
type.getPropertyDefinitionTemplates().add(propDefn);
// Create a template for the child node definition ...
NodeDefinitionTemplate childDefn = mgr.createNodeDefinitionTemplate();
childDefn.setName("ns:node");
childDefn.setRequiredPrimaryTypeNames(new String[]{"ns:reqType1","ns:reqType2"});
childDefn.setDefaultPrimaryTypeName("ns:defaultType");
childDefn.setMandatory(true);
childDefn.setAutoCreated(true);
childDefn.setProtected(true);
childDefn.setSameNameSiblings(true);
childDefn.setOnParentVersion(OnParentVersionAction.VERSION);
type.getNodeDefinitionTemplates().add(childDefn);
// Now register our node type template ...
NodeTypeDefinition[] nodeTypes = new NodeTypeDefinition[]{type};
mgr.registerNodeTypes(nodeTypes, true);
As you can see, even registering a simple node type requires quite a bit of code.