JSR provides versioning as an optional feature. Versioning allows the repository to track different versions of the content as changes are made throughout the content lifecycle. Versioning is built on top of the existing concept of nodes and workspaces, the JCR simply provides a number of convenience methods around these.
JR283 enhances on the versioning set forth in JSR170 by specifying four versioning models, simple versioning, full versioning, activities, and configurations. While these different models provide an interesting intellectual excursion, this section will limit itself to the versioning model built into JSR170, providing the basic understanding of the complexities of versioning content.
To verify that a particular JCR implementation supports versioning, the Repository object can be queried for the feature in the following manner: Repository.getDescriptor(“OPTION_VERSIONING_SUPPORTED”). A repository which supports versioning contains a special storage area called the version storage. The entire version history of a particular node is contained within this version storage.
For a node type to be versionable, it must inherit from the mixin type mix:versionable, either through the node type definition of by adding the mixin type at runtime. The mix:versionable type, a subtype of mix:referencable, adds a number of properties to the node type, such as references to the version history, the base version, and predecessor versions.
The JCR can track all changes of a node, its properties and child nodes. Every check-in operation though Node.checkin() creates a new version. This naturally will lead to a lot of intermediate versions throughout the content lifecycle which might require to be cleaned up. A common practice is to remove intermediate versions when a piece of content is published, only tracking the published versions of a particular node. Another approach may to utilize a version collector which removes older versions after set criteria, such as time. The JCR does not provide this as built-in functionality at the moment.
The node’s version history can be access through the method Node.getVersionHistory() which yields an object of type VersionHistory. This provides access to all versions as well as support to add version labels to particular versions. Version labels can be thought of logical markers which can be used to restore a node to a particular version with the given marker. This provides a mechanism to attach a logical version name across many node’s versions, much like a tag in a source control system.
Under the hood, the mix:versionable node type adds the property jcr:versionHistory, to the versionable content node. This property points to the version history. The version history itself is simply a node of type nt:versionHistory. The version history points to the root version, a node of type nt:version. This node contains version-specific metadata such as when the version was created, the version number, as well as reference properties for the predecessor and successor versions in the version history, essentially forming a doubly-linked list. A new version node is created every time the Node.checkin() method is called through the API.
The properties and child nodes of the original versionable content node are versioned into a subnode of the nt:version node which is referred to as a frozen node, of type nt:frozenNode. The node type descriptor gives the programmer full control over how and if properties and child nodes are versioned. The frozen node contains all copied versionable attributes of the original versionable node as well as the field jcr:frozenPrimaryType contains the node type name of the original node.
The exact semantics of the versioning as well as restore operation for properties and child nodes can be greatly influenced via the node type definition, adding more complexity and choices for the developer. JSR 170 defines four versioning choices, known as the on-parent-version (OPV) attribute, which govern the versioning behavior of properties and child nodes. As a matter of fact, these attributes influence the creation of new versions via the Node.checkin() method as well as the restore semantics, when a node is restored from an older version via the Node.restore() method. The possible OPV attribute values are copy, version, initialize, compute, ignore, abort.
The COPY Attribute
Properties of the versionable node with the copy attribute are directly copied into the frozen node. The referential integrity constraint on nodes of type Reference is lifted when they are copied. This prevents situations in which a node cannot be deleted because an older version of another node might still reference it in a Reference property.
All child nodes of the versionable node are converted into frozen nodes and copied into the version store, independent of their respective node type’s OPV attribute and whether they inherit from mix:versionable or not.
When a node is restored from a previous version via the Node.restore() method, all properties marked with the COPY attribute are restored and all child nodes attached the node are replaced with the versioned nodes.
Use this attribute for all versionable properties. Only use it for a child node definition if the child nodes are dependent children of the parent node and need to be versioned together with the parent node, i.e. the object-oriented equivalent of a composition relationship. If used incorrectly, this can very quickly create a huge versioning graph in your repository or result in undesired behavior then restoring older versions.
The VERSION Attribute
When the OPV attribute of a property definition is set to VERSION, the semantics are identical to those of the COPY attribute: The properties are copied to the versionable node, the referential integrity constraint is lifted for Reference properties.
When specified for child nodes, the versioning operation will honor the specific child node’s individual versioning attributes. Instead of copying versionable child nodes, the JCR creates nodes of type nt:childVersionHistory in the version store which points version history of the child node, not copying the affected subtree. Note that this does not point to the specific version of the child node but the version history, allowing the programmer to delete intermediate versions of nodes without breaking the version graph.
Child nodes which are not versionable are still copied as versionable nodes into the version node of the closes parent node which is versionable.
Let’s explore this by an example. Assume the nodes a,b,c, and d are in the parent-child relationship as outlined on the left side of the picture below. Both nodes types of a an b are versionable (i.e. inherit from mix:versionable) whereas d and b do not. When a is checked in, a frozen node representing a is created. This node contains two child nodes: a frozen node which features the versioned content of the non-versionable node b. And a node of type nt:versionHistory which points to the version of node b which was a child of a at the time it was checked in.
When a node is restored from a particular version, the API provides to options with the removeExisting parameter of the Node.restore() method. If set to true, any children that already exists as children of the node to be restored are replaced directly from the version history. If the flag is set to false and a child already exists, an Exception is thrown. In the above example, this would mean that the call Node.restore(“1.0”, false) on node a would throw an exeption since the versioned subnode b already exists in the current version and hence cannot be replaced as per the parameter.
The INITIALIZE Attribute
When a versionable node is checked in, i.e. a new version created, the new version will contain the child nodes and parameters marked with the INITIALIZE attribute, however, their values will not be copied. Rather, the JCR will create new attributes and child nodes in the version strore and initialize them according to the rules set forth in the node type descriptor, such as default values.
When a versionable node is restored from a previous version, child nodes and properties marked with this OPV attribute are not restored, i.e. ignored.
This behavior merely provides any benefit for day-to-day content modeling, yet it versions the fact that certain properties and child nodes where present at the time of archival.
The COMPUTE Attribute
When the COMPUTE attribute is present for a child node definition, all child nodes, which are also versionable, are copied into the version store as frozen nodes. Upon the restore operation, however, they are ignored. The same holds for property definitions: The properties are versioned but not restored.
This attribute essentially implements a version-only strategy, which copies versionable child nodes, ignores nonversionable nodes, and does not restore the child nodes and properties.
Finding a useful scenario for these semantics is left as an exercise to the reader.
The IGNORE Attribute
When this attribute is present for the child definition of a versionable node, all children will be ignored, i.e. not versioned when the node is checked in. When the node is restored from a previous version, all existing child nodes remain, i.e. the parent-child relationship is neither versioned nor restored.
When the attribute is present for properties, these properties are ignored when a version is created. When a node is restored, and a property marked with this is present, the value is kept.
Properties and child relationships marked with this attribute are essentially ignore in the versioning and restore process.
This is highly useful for properties and child relationships, which do not require being versioned.
The ABORT Attribute
The ABORT attribute provides rather peculiar semantics. Its presence for either the child definition or a property causes the versioning operation to immediately abort with a VersionException, preventing the check-in of such a node.