Creating and Accessing Content

A workspace is defined to always contain exactly one root node which can be obtained by calling Session.getRootNode(). This node is the parent for all custom nodes created via the API. The root node has no name (Session.getRootNode(). getName() returns an empty string). The root node further does not allow child nodes with conflicting names, meaning that the names of all child nodes have to be unique, much like a folder within Windows Explorer. Nodes can further be obtained from the Session object by directly specifying an identifier (via Session.getNodeByUUID( String uuid)).

Adding nodes can be achieved in a variety of ways. For one, the method Node.addNode(String relpath) adds a new node of type nt:unstructured at the given path, relative to the node the method is called on. The method Node.addNode(String relPath, String primaryNodeTypeName) adds a node of a given type.

Properties can be added to nodes via the Node.setProperty(String propertyName, [type] value) method, and removed via Node.removeProperty(String propertyName). If the autocreate option is set in the definition of the property, the JSR will automatically create the property when the parent node is created. Properties defined as mandatory have to be present and set to a value, either explicitly or implicitly be defining a default value in the property definition, before the node can be saved.

All items can be retrieved through using a path expression. Paths uniquely identify every node and property within the repository. They may be written relative to a node or in absolute notation, beginning with a “/” (forward slash). The characters . and .. specify the current and parent node, respectively.

When nodes contain child nodes of the same name, the position of the child node can be specified as part of the path using brackets, for example /a/b[3] matches the third child node with the name b.

The method Node.getNode(String relPath) only accepts relative paths and returns the node matching the relative path given. The method Session.getItem(String path) only accepts absolute paths and returns any item (nodes and properties) matching the given path.

In the following example, the node a,b,c,d, and e are placed in an inheritance relationship, i.e. b is a child node of c, etc.

Sample Nodes

Sample Nodes

The following code example illustrates the various ways of accessing items via paths:

// yields empty string (the name of the root node)
System.out.println(session.getItem("/").getName());

// yields "a"
System.out.println(root.getNode("a/b/c/../..").getName());

// yields "b"
System.out.println(root.getNode("a/b").getName());

// yields "d"
System.out.println(b.getNode("d").getName());

// first child node called a, yields "a"
System.out.println(session.getItem("/a[1]").getName());

// retrieve b's title property, yields "b's title"
Item item = session.getItem("/a/b/samples:title");
if(!item.isNode())
  System.out.println(((Property)item).getValue().getString());

The APIs also implement full visitor pattern, allowing the traversal of all property and node items in the content graph. The following example will print all child nodes and their property names when applied to a node via Node.accept(new PrintingVisitor()).

protected static class ItemNamePrinter implements ItemVisitor {
    public void visit(Property property) throws RepositoryException {
        System.out.println("    property " + property.getName());
    }
    public void visit(Node node) throws RepositoryException {
        System.out.println(" Node Path: " + node.getPath());
        // go through all properties
        for (PropertyIterator propIter = node.getProperties();
             propIter.hasNext();) {
            Property property = propIter.nextProperty();
            property.accept(this);
        }
        // and child nodes
        for (NodeIterator nodeIterator = node.getNodes();
          nodeIterator.hasNext();) {
            Node subNode = nodeIterator.nextNode();
            subNode.accept(this);
        }
    }
}

Content may further be accessed via the query APIs which are covered in-depth in section Content Search.

Creating Content: A First Example

In the previous example [see section Managing Highly Structured Content] a page with content modules was expressed in the form of this UML diagram, which is equivalent to the content model for the simple example web site.

Sample Content Model

Sample Content Model

In this example, we would define the node types Page, ContentModule, FormattedTextModule, and Teaser. All content nodes in the repository have to be of one of these types. The properties (such as the page title or the leftColumn relationship between Page and ContentModule) are modeled using property types.

Given this, a content model for the above example site may define the following node types. All code samples below use the JCR compact node type notation (CND), which will be covered in-depth later.

Node Type: content

This node type is defined as the top-most level of abstraction for all content nodes to be stored in our custom content model. It inherits from the built-in base type mix:referenceable, which allows other properties to reference this node as well as the implicit creation of a jcr:uuid property of the node.

// base class for all content
[samples:content]  > mix:referenceable, nt:base
  // this is a primary node type

Node Type: folder

Type folder node type is introduced in order to organize content pages and modules into folders. Note that the JCR technically defines a built-in type nt:folder, however, this type only allows the pre-defined types nt:folder, nt:file, and nt:linkedFile as children. It hence cannot be used as a means to organize custom content.

The folder type allows for child nodes of type content:

// base class for all content
[samples:content]  > mix:referenceable, nt:base
  // this is a primary node type
  primary

Node Type: contentModule

The contentModule node type serves as a common base class for the teaser and formattedText types. By building this layer of abstraction, the page type can aggregate both types by referencing this type.

// the content module type
[samples:contentModule]   > samples:content
  // headline property
  - samples:title (STRING) mandatory

Node Type: teaser

A teaser is a simple teaser node type which contains a headline property.

// the teaser type which inherits from contentModule
[samples:teaser]  > samples:contentModule
  // no properties (headline is inherited from contentModule)

Node Type: formattedText

A simple formatted text node type which contains a string field.

// the rich text module type which inherits from contentModule
[samples:formattedText]   > samples:contentModule
  // string property
  - samples:formattedTextField (STRING) mandatory

Node Type: page

The page serves as the main content container to build pages for the example layout. It contains multi-valued properties (lists), which aggregate content modules in the left column and sidebar column. Both these multi-valued properties are constrained to type contentModule, meaning they can only reference nodes of type contentModule or subtypes thereof:

Node Type: page The page serves as the main content container to build pages for the example layout. It contains multi-valued properties (lists), which aggregate content modules in the left column and sidebar column. Both these multi-valued properties are constrained to type contentModule, meaning they can only reference nodes of type contentModule or subtypes thereof:

// the page type
[samples:page]   > samples:content
  // title field of type string
  - samples:headline (STRING) mandatory

  // left column as a multi-valued reference property constrained to
  // referenced of nodes of type contentModule only
  - samples:leftColumn (REFERENCE) multiple
    < 'samples:contentModule'

  // left column as a multi-valued reference property constrained to
  // referenced of nodes of type contentModule only
  - samples:sidebar (REFERENCE) multiple
    < 'samples:contentModule'

After importing this definition file into the content repository, we can make the example site come to life. The following Java code creates the necessary nodes for a simple page:

Repository repository = new TransientRepository();
Session session = repository.login(
     new SimpleCredentials("username", "password".toCharArray()));
Node root = session.getRootNode();
Workspace ws = session.getWorkspace();            

// the value factory is to create values or properties
ValueFactory valueFactory = ws.getSession().getValueFactory();            

// create a folder to store the content of the page in
Node folder = root.addNode("demo", "samples:folder"); 

// start creating the content, teaser1 first
Node teaser1 = folder.addNode("teaser1", "samples:teaser");
teaser1.setProperty("samples:title", "Teaser 1 Title");

Node teaser2 = folder.addNode("teaser2", "samples:teaser");
teaser2.setProperty("samples:title", "Teaser 2 Title");

Node teaser3 = folder.addNode("teaser3", "samples:teaser");
teaser3.setProperty("samples:title", "Teaser 3 Title");

Node formattedText = folder.addNode("formattedText1",
 				"samples:formattedText");
formattedText.setProperty("samples:title", "Formatted Text Title");
formattedText.setProperty("samples:formattedTextField",
 				"Hello  my friend");

Node page = folder.addNode("index", "samples:page");
page.setProperty("samples:headline", "Page headline");

// since this property is multi-valued, it can reference any
// number of nodes. The following code utilizes the ValueFactory
// to create value object from the nodes.
page.setProperty("samples:leftColumn", new Value[]{
valueFactory.createValue(teaser1),
     valueFactory.createValue(formattedText)
     });

page.setProperty("samples:sidebar", new Value[]{
valueFactory.createValue(teaser2),
     valueFactory.createValue(teaser3)
     });

session.save();

Mixins

The JCR allows two types of nodes: the primary node type, which we have seen in previous examples, and nodes of type nt:mixin. By definition, a mixin is a type, which is solely defined to serve as a base class for other types. While classic inheritance models the is-a relationship, mixins are used to inherit functionality and property definitions only. Mixin nodes are declared in the same way as primary nodes. By definition, mixin nodes cannot be instantiated, such as via the addNode function. The JCR provides a number of built-in mixins that are to be used when defining custom content models. For example, the built-in mix:referenceable mixin type infuses the behavior of being able to be referenced from property into a node type and adds the jcr:uuid field.

Mixins provide an easy way to define abstract node types in custom content models. As a matter of fact, JSR 170 does not provide the ability to declare abstract node types. This feature is part of JSR 283.

Unlike primary node types, mixins can be added to an existing node even if the mixing declaration is not present in the content model definition.

Let’s extend the previous example by a mixin type named samples:configurable. This type will allow the folder node type to accept an arbitrary number of strings as configuration options. This could, for example, be used to set properties for an entire site section on a per-folder basis. An example would be defining the color scheme to be used by the rendering stack:

// configurable type
[samples:configurable]
  // specify this to be a mixin type
  mixin
  // declare a multi-valued property
  - samples:option (STRING) multiple  

// base class for all content
[samples:content]  > mix:referenceable, nt:version   

// a folder type to organize our pages
[samples:folder]   > samples:content, samples:configurable
  // child nodes are any node of type samples:content
  + * (samples:content) multiple

Which can now be accessed via the API:

// create a folder to store the content of the page in
Node section1 = root.addNode("section1", "samples:folder");
section1.setProperty("samples:option", new String[]
 						{"colorscheme=green"} );
Node section2 = root.addNode("section2", "samples:folder");
section2.setProperty("samples:option", new String[]
 						{"colorscheme=green"} );

Storing Unstructured Content

As mentioned above, every node in the repository must be assigned a type. When creating a new sub node via the API, the type is either directly supplied or, when omitted, the API creates a node of the built-in type nt:unstructured, which is used to model unstructured data. As a matter of fact, there are multiple ways of modeling unstructured data:

Binary properties: Properties of type Binary can be used to store arbitrary binary data

Unstructured node type: The built-in nt:unstructured node type can be utilized to create arbitrary nodes structures. It allows any number of properties of all types as well as any number of sub node. This approach is typically useful for storing semi-structured content, that being content which is stored in a structured fashion yet does not adhere to a specific schema. Examples applications are, for example, XML and HTML data.

nt:file node type: The built-in nt:file node type is designated to represent arbitrary files in the repository. Nodes of this type have one child node by the name content which contains the contents of the file. Typically, the content subnode will be of the built-in type nt:resource which contains a binary property by the name of data.

Pages: 1 2 3 4 5 6 7 8 9 10 11