Angel \”Java\” Lopez on Blog

December 6, 2011

AjCoRe, a simple Content Repository (1) First Steps

Next Post

Some years ago, I discovered Apache Jackrabbit, open source project that implements JSR170 (see first my links (2008) at http://delicious.com/ajlopez/jsr170), but I didn’t pay attention to them. Past week, in an private email list, the content repository topic raised again, so I read some links:

http://en.wikipedia.org/wiki/Content_repository
http://en.wikipedia.org/wiki/Content_repository_API_for_Java
http://www.jcp.org/en/jsr/detail?id=170
http://jcp.org/en/jsr/detail?id=283

More links at

http://delicious.com/ajlopez/contentrepository

The first paper I read was Roy Fielding’s overview:

http://www.day.com/content/dam/day/whitepapers/JSR_170_White_Paper.pdf

The second one (more detailed, it is an specification) was the JSR283 spec:

http://download.oracle.com/otndocs/jcp/content_repository-2.0-pfd-oth-JSpec/

After quick reading both papers, I started to think how to implement it (using .NET; there is an open source implementation, SenseNet). I didn’t see the code or the API described in the JSRs. I want to have a clear, simple idea of what is essential, the core concepts to implements. Then, last weekend, I did a code kata: my first steps in AjCoRe, simple Content Repository:

https://github.com/ajlopez/AjCoRe

using TDD to pratice, as usual (you can see the Git log to view the evolution of my ideas and tests).

The key points are:

– There are Workspaces identified by name
– Any Workspace has a Root Node
– Any Node has properties
– A Property has a name and a value (a simple one, like String, DateTime, int, not a complex object)
– A Node can have a Child Nodes (an enumeration that can be empty)

Initially, in my first code and tests, I could create a Node directly using a public constructor. But in the current state, I prefer to use a controlled entry point for main operations, a Session. The client code/user should create a Session that manage a Workspace.

Now, I have TWO implementations of Workspace and Nodes (after a refactor step, I have INode and IWorkspace interface, and concrete implementations of them). As a proof of concept (mentioned in Fielding’s paper), I want to have a directory in a FileSystem, represented by read-only nodes, with FileNode, and DirectoryNode. Some tests:

[TestMethod]
[DeploymentItem("Files/FileSystem", "fs")]
public void RootNodeProperties()
{
    Workspace workspace = new Workspace("fs", "fs");
    INode root = workspace.RootNode;
    DirectoryInfo info = new DirectoryInfo("fs");
    Assert.AreEqual(info.Extension, root.Properties["Extension"].Value);
    Assert.AreEqual(info.FullName, root.Properties["FullName"].Value);
    Assert.AreEqual(info.Name, root.Properties["Name"].Value);
    Assert.AreEqual(info.CreationTime, root.Properties["CreationTime"].Value);
    Assert.AreEqual(info.CreationTimeUtc, root.Properties["CreationTimeUtc"].Value);
    Assert.AreEqual(info.LastAccessTime, root.Properties["LastAccessTime"].Value);
    Assert.AreEqual(info.LastAccessTimeUtc, root.Properties["LastAccessTimeUtc"].Value);
    Assert.AreEqual(info.LastWriteTime, root.Properties["LastWriteTime"].Value);
    Assert.AreEqual(info.LastWriteTimeUtc, root.Properties["LastWriteTimeUtc"].Value);
    Assert.AreEqual("fs", workspace.Name);
    Assert.IsNotNull(workspace.RootNode);
    Assert.AreEqual(string.Empty, workspace.RootNode.Name);
    Assert.IsNull(workspace.RootNode.Parent);
}
[TestMethod]
[DeploymentItem("Files/FileSystem", "fs")]
public void GetFilesFromRoot()
{
    Workspace workspace = new Workspace("fs", "fs");
    INode root = workspace.RootNode;
    Assert.IsNotNull(root.ChildNodes["TextFile1.txt"]);
    Assert.IsNotNull(root.ChildNodes["TextFile1.txt"]);
}
[TestMethod]
[DeploymentItem("Files/FileSystem", "fs")]
public void GetFileProperties()
{
    Workspace workspace = new Workspace("fs", "fs");
    INode root = workspace.RootNode;
    INode file = root.ChildNodes["TextFile1.txt"];
    FileInfo info = new FileInfo("fs/TextFile1.txt");
    Assert.AreEqual(info.Extension, file.Properties["Extension"].Value);
    Assert.AreEqual(info.FullName, file.Properties["FullName"].Value);
    Assert.AreEqual(info.Name, file.Properties["Name"].Value);
    Assert.AreEqual(info.CreationTime, file.Properties["CreationTime"].Value);
    Assert.AreEqual(info.CreationTimeUtc, file.Properties["CreationTimeUtc"].Value);
    Assert.AreEqual(info.LastAccessTime, file.Properties["LastAccessTime"].Value);
    Assert.AreEqual(info.LastAccessTimeUtc, file.Properties["LastAccessTimeUtc"].Value);
    Assert.AreEqual(info.LastWriteTime, file.Properties["LastWriteTime"].Value);
    Assert.AreEqual(info.LastWriteTimeUtc, file.Properties["LastWriteTimeUtc"].Value);
}
[TestMethod]
[DeploymentItem("Files/FileSystem", "fs")]
public void GetDirectoriesFromRoot()
{
    Workspace workspace = new Workspace("fs", "fs");
    INode root = workspace.RootNode;
    Assert.IsNotNull(root.ChildNodes["Subfolder1"]);
    Assert.IsNotNull(root.ChildNodes["Subfolder2"]);
}

Some notes:

Workspace is AjCoRe.FileSystem.Workspace class in the above code.

– It’s constructor takes two arguments: the name of the workspace in the content repository, and the directory name (maybe relative) that it represents.

– The INode objects are instance of concrete class FileNode or DirectoryNode.

– The properties of file and directory nodes reflect the simple values you find in FileInfo and DirectoryInfo System.IO .NET objects)

DirectoryNode ChildNodes property is a dynamic one: it is built in EACH invocation (I could have adopted a lazy approach, but in this way, the node collection reflects the CURRENT state of the file system):

public NodeList ChildNodes
{
    get
    {
	NodeList nodes = new NodeList();
	foreach (var di in this.info.GetDirectories())
	    nodes.AddNode(new DirectoryNode(this, di.Name, di));
	foreach (var fi in this.info.GetFiles())
 	    nodes.AddNode(new FileNode(this, fi.Name, fi));
	return nodes;
    }
}

It’s time to present the two main abstractioncs, INode:

public interface INode
{
    string Name { get; }
    INode Parent { get; }
    PropertyList Properties { get; }
    NodeList ChildNodes { get; }
    string Path { get; }
}

and IWorkspace:

public interface IWorkspace
{
    string Name { get; }
    INode RootNode { get; }
}

Notice that I didn’t need have a unique Identifier for a node in workspace. Every Node has a path (the concatenated names of their parents, using / as separator). I should implement the retrieve of a particular node using its path. I didn’t need a NodeType, yet. I’m following the YAGNI principle 😉

The other IWorkspace/INode concrete implementation manage node and properties in memory. The nodes can be created and removed by code, and the property values can be changed. It’s my main implementation that I want to extend. The key piece to add: an IStore that can retrieve and save modified nodes to persistence store (many implementations: database, NoSql, Azure blob storage, Json files (representing node properties) in file system (representing the node hierarchy), cloud file system, Azure tables, etc…).

The creation of AjCoRe.Base.Workspace:

Workspace workspace = new Workspace("ws1", null);

The second parameter is the list of properties to put in the new Root Node of the new in memory workspace.

Then, you can get a session to it:

Session session = new Session(workspace);

You can navigate the node hierarchy as in the previous example, using workspace.RootNode and the ChildNodes enumeration. BUT, to modify then, you SHOULD use a transaction:

INode node = session.Workspace.RootNode;
using (var tr = session.OpenTransaction())
{
    session.SetPropertyValue(node, "Name", "Adam");
    session.SetPropertyValue(node, "Age", 800);

    tr.Complete();
}

You MUST commit the transaction explicity with tr.Complete(). If you missed it, the changed properties are restored to their previous values. Creation, removing of nodes and properties are also tracked during a transaction. You can create a new node with their initial properties:

INode root = session.Workspace.RootNode;
using (var tr = session.OpenTransaction())
{
    INode node = session.CreateNode(root, "person1", new List<Property>()
    {
        new Property("Name", "Adam"),
        new Property("Age", 800)
    });

    tr.Complete();
}

Or you can add, change, remove (setting its value to null) properties in a transaction:

INode root = session.Workspace.RootNode;
using (var tr = session.OpenTransaction())
{
    session.SetPropertyValue(root, "Name", "Adam");
    session.SetPropertyValue(root, "Age", 800);
    tr.Complete();
}

Then, using the session as entry point for modifications, I could track the changes in a Unit of Work, without using observers over properties in nodes (if I adopted that approach, probably I should manage to have observers for every node that the client code could traverse in the workspace). I have plans to implements something like Software Transactional Memory to support concurrency (I already have code for that feature in AjTalk and AjSharp).

Next posts: implementation details (transactions, session factories, workspace registries, etc..)

Next steps: implement persistence in a store, concurrent transactions.

Keep tuned!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

1 Comment »

  1. […] Previous Post […]

    Pingback by AjCoRe, A Simple Content Repository (2) Stores « Angel “Java” Lopez on Blog — December 14, 2011 @ 10:04 am


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: