Traversal

How do we test that node was actually added and persisted to content repository? Implement the getBlogList() method of BlogEntryDAO method, which returns a list of all child nodes of root node whose name is equal to blogEntry. The following code listing demonstrates how to do that:

Once you have a root node object, you can call getNodes() on it to return all its child nodes. If the node does not have any children, then an empty NodeIterator is returned. We can iterate through NodeIterator to get a list of blogEntry nodes. You can call the node's getProperty() to read a property with a supplied name. getProperty() returns an instance of Value, whose implementation class depends on the type of property stored. Once you have this object you can call type-specific methods such as getString() for reading a string stored in the property, or getDate() for a stored date.

When you go to http://localhost:8080/<contextroot> in the web UI, the index page of the blog application will call the getBlogList() method and it will display all entries on index page.

Searching for Content (XPath)

JSR-170 defines two ways to search for content. One uses XPath syntax and the other uses SQL syntax. The specification mandates that every Level 1 compliant repository should provide support for XPath syntax, but support for SQL search is an optional feature that we will talk more about in the next part.

XPath is a search language originally designed for selecting elements from an XML document. Since a workspace, like an XML document, can be viewed as a tree structure, XPath provides a convenient syntax for searching workspace content.

Let's change our blogging application so that it allows you to search for all blog entries posted by a particular user (i.e., all blog entries where blogAuthor is some user name). We need two things to implement this: one is a change to the UI to accept the query userName and display results to the user. This feature can be seen in the sample application, which displays an input box named Blogger Name at the top of page, with a "Search" button. When you input text in this box and click search, control goes to SearchBlogEntriesAction.java, which calls the searchBlogList() method of BlogEntryDAO with the blogger name supplied by the user, and displays results as a list. So, the only thing that we have do is implement the searchBlogList() method of JackrabbitBlogEntryDAO class like this:

Once we have the session object, call the getWorkspace() method on it to retrieve the workspace attached to the current session. Remember, there is a one-to-one mapping between workspace and session. This workspace object can be used to retrieve the QueryManager associated with this workspace. The QueryManager interface encapsulates methods for management of search queries. The next thing that we want to do is to create a query string, which in our case would be "//blogEntry[@blogAuthor='<bloggerName>'"]. This means "search all nodes with name equal to blogEntry and value of blogAuthor property equal to the <bloggerName> supplied by user". See the JSR-170 specification document for more details on the syntax of an XPath query.

You can create a new query object by calling the queryManager's createQuery() method, passing a query string and the name of the query language (XPath in our case). Once you have the Query object, you can call its execute() method to actually execute the query and return a QueryResult object. The results returned always respect the access restrictions of the current session. In other words, if the current session does not have read permission for a particular item, then that item will not be included in the result set, even if it would otherwise constitute a match. All queries are run against the peristent state of the workspace; pending changes stored in the session are not searched. Once you have results you can call getNodes() method on it to have an iterator go over nodes that match the query.

Two more methods that we want to implement are updateBlogEntry() and removeBlogEntry(), which are actually very simple. In both these methods, we retrieve the relevant node by using the title as the primary key. In the update case, set the new values for properties to update, and in the remove case, once you have have target node call the remove() method on it. Don't forgot to call session.save() method to make sure that your changes are persisted.

Handling Binary Bontent

One of the primary requirements of a content repository is that it should be able to handle binary content, such as image files. Now let's assume that we want to allow the user to attach an image to a particular blogEntry and we also want to add a retrieval method. To do that, we have added two links to every blog entry in its title bar: "attach file" for attaching an image to a blog entry and "display attached file," which displays the image attached to that blogEntry. To get this feature working we have to implement two methods from BlogEntryDAO: attachFileToBlogEntry() and getAttachedFile() :

As you can see from this code listing, the repository does not treat binary content any different from any other type of content. The only difference is you can add binary content by setting InputStream as the value of property; the same goes for retrieving a property with a binary value. Where this file is actually stored is determined by the value of the externalBLOBs attribute in your persistent manager. If its value is true, then this image file will be stored on filesystem, and if false it will be stored in the database as a BLOB. In our case this value is true, so the uploaded image file will be stored in the filesystem.

Summary

By now you should have a good understanding of JSR-170, Jackrabbit, and how to develop simple applications using JSR-170 API. In this article, our discussion was focused mostly on the basics of JSR-170 and Apache Jackrabbit. We started our discussion by talking about the Java Content Repository API and the benefits of standardization in this space. We then covered the repository model defined by JSR-170 and how to download, configure, and run Apache Jackrabbit. After that, we developed a sample blogging application for demonstrating the basic features of JSR-170 API.