Custom properties on nt:file and nt:folder nodes

One really nice feature of JCR repositories is that you can use them to store files and folders. Like with all other content, the structure of these nodes is dictated by their node types, and most people use the “nt:file” and “nt:folder” node types defined in the JCR specification. Learning to use these node types can take a little work, because they’re not quite as straightforward as you might expect.

Consider a “MyDocuments” folder that contains a “Personal” folder and a “Status Report.pdf” file. Here’s what those nodes might look like:

The folders look like what you might expect: they have a name, a primary type of “nt:folder”, and the “jcr:createdBy” and “jcr:created” properties defined by the “nt:folder” node type. (These properties are defined as ‘autocreated’, meaning the repository should set these automatically.)

The file representation, on the other hand, is different. The “Status Report.pdf” node has a primary type of “nt:file” and the “jcr:createdBy” and “jcr:created” properties defined by the “nt:file” node type, but everything about the content (including the binary file content in the “jcr:data” property) is actually stored in the child node named “jcr:content”. This may seem odd at first, but actually this design very nicely separates the file-related information from the content-related information.

Think about how an application might navigate the files and folders in a repository. Using the JCR API, the application asks for the “MyDocuments” node, so the repository materializes it (and probably its list of children) from storage. The application then asks for the children, so the repository loads the “Personal” folder node and the “Status Report.pdf” node, and there’s enough information on those nodes for the application to display relevant information. Note that the “Status Report.pdf” file’s content has not yet been materialized. Only when the application asks for the content of the file (that is, it asks for the “jcr:content” node) will the content-related information be materialized by the repository. (And, some repository implementations might delay loading the “jcr:data” binary property until the application asks for it.) Nice, huh?

Another interesting aspect of the “nt:file” and “nt:folder” node types (and even the “nt:resource” node type) is that they don’t allow adding just any property on the node. The beauty is that they don’t have to, because you can still add extra properties to these nodes using mixins!

Let’s imagine that we want to add tags to our file and folder nodes, and that we want to start capturing the SHA-1 checksum (as a hexadecimal string) of our files. To start, we need to create two mixins (we’ll use the CND format):

(We could have defined a mixin that allows any property, similar to how the standard “nt:unstructured” node type does it. Then, we can add any properties we want. However, I tend to like using more targeted mixins like these, if for no other reason than it makes it very easy to use JCR-SQL2 to query the nodes that use these mixins.)

We then need to register these node types in our repository (perhaps by loading the CND file or programmatically using the NodeTypeManager). Then, we can add the “acme:taggable” mixin to whatever file and folder nodes we want. This is as simple as: