ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Advanced Java Content Repository API

by Sunil Patil
11/08/2006

Java Content Repository (JCR) API, specified as JSR-170, is an attempt to standardize an API used for accessing content repositories. In this article, we'll talk about the advanced and optional features defined in the JCR API. We assume that you're already familiar with basic features of JCR--such as how to add a new node or property, how to configure Apache Jackrabbit, etc. If you are not familiar with these topics or just want to refresh your memory, please check out the What is Java Content Repository article first.

In this article, we'll use Apache Jackrabbit, which is the reference implementation of JSR-170 and an open source project hosted on Apache, but you can use any other JSR-170-compliant content repository of your choice.

Content management systems from different vendors have been available for quite some time. All of these vendors ship their own version of a content repository with their CMS systems, usually providing a proprietary API for accessing their content repository. One of the primary goals of JSR-170 was to make it easy for these CMS vendors to adopt JSR-170. To do that, JSR-170 features are divided into three parts. First are the Level 1 features, which provide a read-only view of the repository. Level 2 defines the basic features required for read and write operations. In addition to these two levels, JSR-170 defines five optional features--versioning, observation, locking, SQL Search, and transactions--that can be implemented by a content repository. That means a vendor who has a Level 1-compliant repository can decide to implement the SQL search feature but none of the other advanced features. A second vendor might implement a different feature set--for example, a Level 2-compliant repository with versioning and observation.

This article is a step-by-step guide on how to develop your application using two of the most popular optional features defined in JSR-170. We will start our discussion with versioning, and then follow with observation, which lets you execute some business logic when a particular persistent change is made in the repository.

Sample Application

In this article, we'll use Apache Jackrabbit to change the sample blogging application developed in the "What is Java Content Repository" API to demonstrate the advanced features defined in JSR-170. Please note that Jackrabbit is not only Level 1- and Level 2-compliant, but it also implements all the optional features of JSR-170.

This sample blogging application is a web application that uses Struts 1.2 as its MVC framework. It allows you to post new blog entries, edit or delete existing blog entries, and attach image files to blog entries. The sample blogging application consists of two parts: a UI part, which handles taking inputs from users and displaying results, and the second part, which actually interacts with the content repository. We have created the BlogEntryDAO interface, which serves as the contract between these two parts. To save some time, we won't talk about how to build UI part; instead, we will concentrate only on data. Please download the sample code and copy the UI code from it directly.

The first thing to do in our sample application is to change the BlogEntryDAO interface as follows to define methods for versioning feature:

public interface BlogEntryDAO {
    //Basic methods implemented in first part
    public ArrayList getVersionList(String blogTitle)
        throws BlogApplicationException;
    public void restoreVersion(String blogTitle,String versionName)
        throws BlogApplicationException;
}

The first method, getVersionList(), takes the title of the blogList and returns a list of all the versions for that blogEntry.The restoreVersion() method is used for restoring a version of a blog entry using the specified version name.

Versioning

The first thing to discuss about versioning is what the term means in the context of a content repository. JSR-170 defines versioning as saving the state of the node to be recorded in such a way that it can later be restored. Since versioning is an optional feature, you should check first whether your repository supports it or not by calling the repository.getDescriptor("OPTION_VERSIONING_SUPPORTED").

In a versioning repository, a workspace can have both a versionable and non-versionable node. You can mark a particular node as versionable by adding mix:versionable as a mixin type to that node. Every versionable node is marked as read-only, so every time you want to make change in it, you need to call node.checkout() first to remove the read-only attribute from that node. Make whatever changes you want and then call node.save() to persist your changes. After that, you should call node.checkin(), which will put the node back in a read-only state and create a new version with a system-generated node.

If your repository is versionable, it will have a special version storage area in addition to one or more workspaces. This area is used for storing version history. The best part about JCR API is that it uses the same structure of nodes and properties for storing version data. It relates one node of nt:versionHistory type with every versionable node. This node contains a history of versions for that particular node and will always have one root node containing the basic version (the state of node at the time of creation). After that, whenever you check in your change, one new version node is created and is related to a previous version of the node by a successor relationship. Every version itself is a node of the nt:version type and will store the state of the node at the time the version was created in the jcr:frozenNode property.

Until now our discussion has focused mostly on theoretical aspects of versioning. Now let's try to take this discussion to the next level by changing our sample blogging application to use versioning. We want to change our sample blogging application so that every blog entry should display a "display version list" link. Each version listed by this link would have a "restore" link to restore the blogEntry to that particular version. Follow these steps to change the sample blogging application:

  1. As usual, you can copy the UI part for this from the sample application code.

  2. Change the insertBlogEntry() method of JackrabbitBlogEntryDAO class like this:

    public void insertBlogEntry(BlogEntryDTO blogEntryDTO) throws BlogApplicationException {
        Session session = JackrabbitPlugin.getSession();
                    Node rootNode = session.getRootNode();
        Node blogEntry = rootNode.addNode("blogEntry");
        blogEntry.addMixin("mix:versionable");
        blogEntry.setProperty(PROP_TITLE, blogEntryDTO.getTitle());
        blogEntry.setProperty(PROP_BLOGCONTENT, blogEntryDTO.getBlogContent());
        blogEntry.setProperty(PROP_CREATIONTIME, blogEntryDTO.getCreationTime());
        blogEntry.setProperty(PROP_BLOGAUTHOR, blogEntryDTO.getUserName());
        session.save();
    }

    First, we want to add a mixin type called mix:versionable to blogEntry. After this, the content repository will put the blogEntry in read-only state after saving it. However, this change would only make new entries versionable, leaving you with a mix of versioned and unversioned entries. You can solve this problem by deleting the existing content repository and adding new blog entries to it, but Apache Jackrabbit does not provide any built-in way to delete a content repository. The only way to do it is to delete the contents of c:/temp/blogging.

  3. Next, change updateBlogEntry() method like this:

    public void updateBlogEntry(BlogEntryDTO blogEntryDTO)
                throws BlogApplicationException {
        Session session = JackrabbitPlugin.getSession();
        Node blogEntryNode = getBlogEntryNode(blogEntryDTO.getTitle(),
                session);
        blogEntryNode.checkout();
        blogEntryNode.setProperty(PROP_BLOGAUTHOR, blogEntryDTO.getUserName());
        blogEntryNode.setProperty(PROP_BLOGCONTENT, blogEntryDTO
                .getBlogContent());
    
        blogEntryNode.setProperty(PROP_CREATIONTIME, blogEntryDTO
                .getCreationTime());
        session.save();
        blogEntryNode.checkin();
    }

    As mentioned in the previous discussion, once you mark a node as versionable, the repository will put it in read-only state, and as a result, you won't be able to make any changes in it. So when updating a blog entry, the first thing that you should do is call checkout(), which will make it writeable. After that, you can change the state of the node by calling setProperty() methods, and then save your changes with session.save(). Finally, call blogEntry.checkin().

  4. Make similar changes in other methods of JackrabbitBlogEntryDAO.

  5. Now, implement the getVersionList() method, which takes the title of a blog entry as a parameter and returns a list of versions for that particular blog entry.

    public ArrayList getVersionList(String blogTitle) throws BlogApplicationException {
        ArrayList versionList = new ArrayList();
        Node blogEntryNode = getBlogEntryNode(blogTitle);
        VersionHistory blogEntryVersionHistory = blogEntryNode.getVersionHistory();
        VersionIterator blogEntryVersionIt = blogEntryVersionHistory.getAllVersions();
    
        blogEntryVersionIt.skip(1);
        while(blogEntryVersionIt.hasNext()){
            Version version = blogEntryVersionIt.nextVersion();
            
            NodeIterator nodeIterator = version.getNodes();
            while(nodeIterator.hasNext()){
                Node node = nodeIterator.nextNode();
                String title  = node.getProperty(PROP_TITLE).getString();
                String blogContent = node.getProperty(PROP_BLOGCONTENT).getString();
                String blogAuthor = node.getProperty(PROP_BLOGAUTHOR).getString();
                String versionName = version.getName();
                Calendar creationTime = node.getProperty(PROP_CREATIONTIME).getDate();
                VersionEntryDTO blogEntryDTO = new 
                    VersionEntryDTO (blogAuthor, title, blogContent,
                                    creationTime, versionName);
                versionList.add(blogEntryDTO);
            }
        }
        return versionList;
    }

    The first step is to call getVersionHistory() to get access to the nt:versionHistory node attached to the blog entry node. A VersionHistory object wraps a nt:versionHistory node. Next, calling getAllVersions() on the VersionHistory object will give you an iterator. A version history will always have at least one version: the root version. You can call the getNodes() method on the version object to access child nodes of that version, which are Version objects (wrapping nt:version nodes). Each child node contains the state of the blogEntry at the time the version was created, and from that state we can create a BlogEntryDTO.

  6. The last step is to implement restoreVersion(), which takes two parameters: a blogTitle to uniquely identify blogEntry, and a versionName.

    public void restoreVersion(String blogTitle, String versionName) 
        throws BlogApplicationException {
        Node blogEntryNode = getBlogEntryNode(blogTitle);
        blogEntryNode.restore(versionName,true);
    }

    Once you have the blogEntry node, you can call restore() with the versionName to be restored. The second parameter to this method is a flag that represents what happens if the UUID of the node you're trying to restore already exists outside the subtree of this node. If so, and if the flag is true, then the incoming node takes precedence.

Now build your source code and deploy this application on the server. First, add a new blogEntry and make a few changes in it by editing that node two or three times. Now click on the "Display Version History" link. This will take you to a page containing a list of versions for that blog entry, and each version listed contains a restore link; when you click on the link for particular version, the blogEntry will be restored to the state stored in that particular version.

Pages: 1, 2

Next Pagearrow