Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Introducing the Java Content Repository API

Learn how JSR-170 makes building CMAs a snap

Titus Barik (titus@barik.net), Senior Programmer Analyst, SoloFX Enterprises, LLC
Titus Barik is a content application developer with an interest in open source enterprise solutions. He has deployed Magnolia and other JSR-170 technologies successfully in corporate and non-profit environments. His personal weblog is available at barik.net, and he welcomes your comments and suggestions.

Summary:  With the growing popularity of content management applications, the need for a common, standardized API for content repositories has become apparent. The Content Repository for Java™ Technology API (JSR-170) aims to provide such an interface. In this article, you'll use the open source Apache Jackrabbit implementation of JSR-170 to explore the features offered by this promising framework by designing a simple Wikipedia-like encyclopedia back end.

Date:  27 Jun 2006 (Published 23 Aug 2005)
Level:  Advanced
Also available in:   Chinese  Japanese

Activity:  140867 views
Comments:  

If you've ever tried to develop a content management application, you're all too aware of the difficulties inherent in implementing content systems. The landscape is fragmented, with numerous vendors offering proprietary repository engines. These difficulties exacerbate the complexity and maintainability of such systems, promote vendor lock-in, and increase the need for long-term legacy support in the enterprise market. With the growing popularity of corporate weblogs and electronic corporate document management, the need for a standardized content repository interface is more apparent than ever.

The Content Repository for Java Technology specification, developed under the Java Community Process as JSR-170, aims to meet these industry needs. The specification provides a unified API under the javax.jcr namespace that allows you to access any specification-compliant repository implementation in a vendor-neutral manner.

But API standardization is not the only feature that the Java Content Repository (JCR) brings to the table. A major advantage of JSR-170 is that it is not tied to any particular underlying architecture. The back-end data storage for a JSR-170 implementation, for instance, may be a filesystem, a WebDAV repository, an XML-backed system, or even an SQL database. Furthermore, the export and import facilities of JSR-170 allow an integrator to switch seamlessly between content back ends and JCR implementations. Finally, the JCR provides a straightforward interface that can be layered on top of a wide variety of existing content repositories, while simultaneously standardizing complex functionality such as versioning, access control, and searching.

There are several approaches that I could take when discussing the JCR. In this article, I examine the features offered by the JSR-170 specification from a developer's perspective, focusing on the available API and the interfaces that allow a programmer to efficiently use the JSR-170 repository in designing a content application. As an artificial example, I'll implement a trivial back end for a Wikipedia-like encyclopedia system, called JCRWiki, with support for binary content, versioning, backup, and search. I use Apache Jackrabbit, an open source implementation of JSR-170, to develop this application.

The Repository model

I'll begin with a high-level discussion of the Repository model to familiarize you with the JCR. The Repository model is a simple hierarchy and looks much like an n-ary tree. It consists of a single content repository, with one or more workspaces. (In this article, I'll limit the discussion to a single workspace.) Each workspace contains a tree of items; an item can be either a node or a property. A node can have zero or more children, and zero or more associated properties, where the actual content is stored.

Every node has one and only one primary node type. A primary node type defines the characteristics of the node, such as the properties and child nodes that the node is allowed to have. In addition to the primary node type, a node may also have one or more mixin types. A mixin type acts a lot like a decorator, providing extra characteristics to a node. A JCR implementation, in particular, can provide three predefined mixin types:

  • mix:versionable: allows a node to support versioning
  • mix:lockable: enables locking capabilities for a node
  • mix:referenceable: provides an auto-created jcr:uuid property that gives the node a unique, referenceable identifier

This structure is illustrated in Figure 1. Circles represent nodes, while rectangles represent properties. Of interest are nodes A, B, and C, descending from the singular root node. Node A has two properties: a string, "John," and an integer, 22.


Figure 1. A repository model with multiple workspaces
A repository model with multiple workspaces

Predefined node types

Every repository must support the primary node type, nt:base. There are a number of other common node types that a repository may support:

  • nt:unstructured is the most flexible node type. It allows any number of child nodes or properties, which can have any names. This node type represents JCRWiki entries.

  • nt:file represents files. It requires a single child node, called jcr:content. This node type represents images and other binary content in a JCRWiki entry.

  • nt:folder node types can represent folders, like those in a conventional filesystem.

  • nt:resource commonly represents the actual content of a file.

  • nt:version is a required node type for repositories that support versioning.

The entire node type hierarchy can be found in section 6.7.22.1 of the JSR-170 specifications (see Resources for a link).

Namespaces

A useful but often overlooked feature of the Repository model is its support for namespaces. Namespaces prevent naming collisions among items and node types that come from different sources and application domains. Namespaces are defined with a prefix, delimited by a single : (colon) character. In the course of this article, you've already encountered the namespaces jcr for JCR internal properties, mix for mixin types, and nt for node types. In the JCRWiki, you'll use the wiki namespace for all your data.


Installing the JCR

At the time of this writing, Apache Jackrabbit, the Apache Foundation's open source implementation of JSR-170, has reached a version 1.0 release. Compiled bytecode JAR files are available for download directly from the Jackrabbit Web site (see Resources). Though Jackrabbit can still be compiled from source using SVN, the Jackrabbit library is stable to the point where nightly builds are no longer necessary. This section provides a step-by-step guide to getting your JCR implementation up and running as quickly as possible.

Required libraries

To use and run the examples in this article, make the following libraries available in your classpath:

  • jackrabbit-core: Jackrabbit content repository core implementation for JSR-170 and common utility code from Apache.

  • commons-collections: A framework that contains powerful data structures that accelerate development of Java applications.

  • concurrent: A library that provides standardized, efficient versions of utility classes commonly encountered in concurrent Java programming.

  • derby: An Apache DB subproject that provides a relational database implemented entirely in the Java language.

  • jcr: A set of interfaces conforming to the JSR-170 specification.

  • log4j: A runtime logging library.

  • lucene: A high-performance, full-featured text search engine library.

  • slf4j (Simple Logging Facade for Java): Intended to serve as a simple facade for various logging APIs allowing the user to plug in the desired implementation at deployment time.

  • xerces: An advanced XML parser supporting SAX Version 2, DOM Level 1, and SAX Version 1 APIs.

All of these JAR files are downloaded during the Jackrabbit build process and are available in the Maven cache directory if you are building Jackrabbit from SVN. Under Linux, these JARs are located under .maven within your home directory. If you're using the binary builds, then simply download the libraries from their respective Web sites or browse the "First Hops with Jackrabbit" tutorial on the Jackrabbit Web site, which provides direct links to all of these resources. The jcr-1.0.jar is additionally available in the JSR-170 specifications download, found on the Java Community Process Web site.


Manual configuration

JSR-170 does not specify exactly how you should obtain a Repository object initially; this is left as an implementation detail for each repository vendor. However, the use of JNDI or some other configuration mechanism in a container environment is preferred in applications to keep your JSR-170 implementation relatively free of direct Jackrabbit dependencies. Though this strategy results in added complexity during initial configuration, it provides greater portability across different JSR-170 implementations. For a less portable but simplified configuration scheme, use automatic configuration, detailed later this article.

In manual configuration, you can obtain the repository with the use of JNDI in combination with a configuration file, called repository.xml, that is loaded programmatically.

Repository configuration

The first and easiest step is to create a repository.xml file for Jackrabbit. This configuration file accomplishes a number of important tasks. Among other things, it specifies the underlying backing store, the access control mechanism, the available workspaces, the versioning system, and the search subsystem. Listing 1 gives an example:


Listing 1. Sample repository.xml configuration file

<?xml version="1.0" encoding="ISO-8859-1"?>
<Repository>
    <FileSystem 
    		 class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
        <param name="path" value="${rep.home}/repository"/>
    </FileSystem>
    <Security appName="Jackrabbit">
        <AccessManager 
        		 class="org.apache.jackrabbit.core.security.
        		 		 SimpleAccessManager"/>
    </Security>
    <Workspaces 
    		 rootPath="${rep.home}/workspaces" 
    		 		 defaultWorkspace="default" />
    <Workspace name="${wsp.name}">
        <FileSystem 
        		 class="org.apache.jackrabbit.core.fs.local.
        		 		 LocalFileSystem">
            <param name="path" value="${wsp.home}"/>
        </FileSystem>
        <PersistenceManager
            class="org.apache.jackrabbit.core.state.xml.
            		 XMLPersistenceManager" />
        <SearchIndex 
        		 class="org.apache.jackrabbit.core.query.lucene.
        		 		 SearchIndex">          
            <param name="path" value="${wsp.home}/index" />
        </SearchIndex>
    </Workspace>
    <Versioning rootPath="${rep.home}/versions">
        <FileSystem 
        		 class="org.apache.jackrabbit.core.fs.local.
        		 		 LocalFileSystem">
            <param name="path" value="${rep.home}/versions"/>
        </FileSystem>
        <PersistenceManager
            class="org.apache.jackrabbit.core.state.xml.
            		 XMLPersistenceManager" />
    </Versioning>
</Repository>

This configuration uses the local filesystem for storing the repository data and a SimpleAccessManager for access control. The rest of the file is largely self-explanatory; you can simply copy it verbatim into your repository directory.

Security configuration

You can provide trivial security by using the JAAS configuration file, jaas.config, placed in your project root directory. Listing 2 offers an example:


Listing 2. Sample JAAS configuration

Jackrabbit {
org.apache.jackrabbit.core.security.SimpleLoginModule required 
		 anonymousId="anonymous";
};

Repository initialization code

The packages detailed in Listing 3 are utilized in initializing the repository:


Listing 3. Initialization import statements for manual configuration

import org.apache.jackrabbit.core.jndi.RegistryHelper;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.jcr.*;
import javax.jcr.query.*;
import javax.jcr.version.*;

import java.util.Hashtable;
import java.util.Calendar;
import java.io.*;

import sun.net.www.MimeTable;

To obtain the Repository object, set the configFile variable to point to the repository.xml file and the repHomeDir variable to the local filesystem directory where you want the repository to reside. When using JNDI in conjunction with the RegistryHelper, it is simple to obtain the repository, as Listing 4 illustrates:


Listing 4. Obtaining a Repository object with JNDI

String configFile = "repository.xml";
String repHomeDir = "repository";

Hashtable env = new Hashtable();
		 env.put(Context.INITIAL_CONTEXT_FACTORY,
		 		 "org.apache.jackrabbit.core.jndi" +
		 		 ".provider.DummyInitialContextFactory");

env.put(Context.PROVIDER_URL, "localhost");

InitialContext ctx = new InitialContext(env);

RegistryHelper.registerRepository(ctx,
		 "repo",
		 configFile,
		 repHomeDir,
		 true);

Repository r = (Repository) ctx.lookup("repo");

Next, SimpleCredentials is used to obtain a Session object. In this implementation, SimpleCredentials accepts any and all usernames. An alternative JCR implementation could provide a more complicated authentication mechanism, perhaps one that connects to an LDAP server or an external database to provide credential information. (The full capabilities of authentication and access control are beyond the scope of this article. For more information, see section 6.9 of the JSR-170 specification.)

The Session object provides the programmer with a transient storage layer, much like those found in traditional object-relational mapping tools, and also serves as a link to a particular workspace. It allows the client to access any node or property that is tied to that session. From the session, you obtain a workspace, from which you can then obtain the root node. All of these steps are accomplished in the brief snippet of code in Listing 5:


Listing 5. Obtaining a workspace and root node

SimpleCredentials cred = new SimpleCredentials("userid",
		 "".toCharArray());

Session session = r.login(cred, null);
Workspace ws = session.getWorkspace();
Node rn = session.getRootNode();

Using the session, workspace, and root node reference, you can now access the repository features through the different abstraction levels. Finally, to verify that the repository has successfully initialized, you can simply print the name of the root node with rn.getPrimaryNodeType().getName(). This should result in the following output:

rep:root

Because you are using JAAS, remember to include -Djava.security.auth.login.config==jaas.config. as one of your Java VM parameters.

JCRWiki namespace

In this exercise, all of your JCRWiki content falls under the wiki namespace. For the repository to recognize this namespace, you must register it during initialization, like so:

ws.getNamespaceRegistry().registerNamespace
		 ("wiki", "http://www.barik.net/wiki/1.0");

Congratulations! The manual configuration for your repository is now complete.


Automatic configuration

The Jackrabbit implementation also provides a TransientRepository class from its core API, which automatically initializes the content repository when the first session is started and shuts it down when the last session is closed. For simple, stand-alone applications, using the TransientRepository greatly simplifies the configuration of the repository, but at the expense of JSR-170 portability.

The TransientRepository automatically creates a repository.xml and repository folder. It also internally provides the SimpleAccessManager to handle authentication and security.

Automatic configuration requires the initialization import statements as shown in Listing 6. In contrast to the manual configuration, all JNDI references are removed. In place of the RegistryHelper, the TransientRepository is substituted.


Listing 6. Initialization import statements for automatic configuration

import org.apache.jackrabbit.core.TransientRepository

import javax.jcr.*;
import javax.jcr.query.*;
import javax.jcr.version.*;

import java.util.Calendar;
import java.io.*;

import sun.net.www.MimeTable;

Because the TransientRepository performs initialization for you, obtaining the repository is quite simple and is shown in Listing 7:


Listing 7. Obtaining a repository, workspace, and root node with the TransientRepository

Repository r = new TransientRepository();
Session session = r.login(new SimpleCredentials("userid", "".toCharArray()));

Workspace ws = session.getWorkspace();
Node rn = session.getRootNode();

As with manual configuration, all of your JCRWiki content falls under the wiki namespace:

ws.getNamespaceRegistry().registerNamespace
		 ("wiki", "http://www.barik.net/wiki/1.0");

Congratulations! The automatic configuration for your repository is now complete.


JCRWiki design strategy

Let's now take a look at the overall JCRWiki repository content hierarchy. In the example, you will create two entries, "rose" and "Shakespeare," both of type nt:unstructured. Each encyclopedia entry, by design contract, has three properties: the title of the entry, the entry content, and either a multivalued category property (if the entry has multiple categories) or a single-valued category property (if the entry has a single category). A multivalued property behaves programmatically as an array of values.

Figure 2 outlines a graphical diagram of the JCRWiki design strategy:


Figure 2. A high-level diagram of the JCRWiki topology
A high-level diagram of the JCRWiki topology

JCRWiki functionality

A repository without any content is not particularly useful. This section demonstrates the basic content manipulation functionality provided by JSR-170 and describes some of the more advanced, optional repository features available, such as versioning and importing and exporting XML content.

Adding content

Begin in Listing 8 by adding content nodes to the repository so that it resembles the JCRWiki topology in Figure 2:


Listing 8. Adding content to a JCR repository

Node encyclopedia = rn.addNode("wiki:encyclopedia");

Node p = encyclopedia.addNode("wiki:entry");
p.setProperty("wiki:title", new StringValue("rose"));
p.setProperty("wiki:content", new 
		 StringValue("A rose is a flowering shrub."));
p.setProperty("wiki:category",
		 new Value[]{
		 		 new StringValue("flower"),
		 		 new StringValue("plant"),
		 		 new StringValue("rose")});

Node n = encyclopedia.addNode("wiki:entry");
n.setProperty("wiki:title", new StringValue("Shakespeare"));
n.setProperty("wiki:content", new 
		 StringValue("A famous poet who likes roses."));
n.setProperty("wiki:category", new StringValue("poet"));

session.save();

By default, nodes in Jackrabbit are set to nt:unstructured. Note that the category property in "rose" is multivalued. The last line of the code snippet saves the session. Recall that adding and setting nodes and node properties only modifies the transient session storage layer. To persist these changes to the repository, you must save the session with session.save(). You can remove nodes by calling Node.remove() on the desired node.

Accessing content

JSR-170 provides two methods for accessing nodes: traversal access and direct access. Traversal access involves walking the content tree using relative paths, while direct access allows you to jump directly to a node with either an absolute path or, if the node is referenceable, with a UUID. Because of the similarities between the two types of access, I'll focus exclusively on traversal access in this article.

Traversal access is available from any Node object and its methods Node.getNode() and Node.getProperty(). Using your JCRWiki topology, you can obtain the encyclopedia node from the root node with the following code:

Node encyclopedia = rn.getNode("wiki:encyclopedia");

You can further traverse down to a property. For example, in the "rose" encyclopedia entry from the root node, assuming prior knowledge of the JCRWiki topology, you can traverse to a property like so:

String roseTitle = rn.getProperty 
  ("wiki:encyclopedia/wiki:entry[1]/wiki:title").getString()

Notice that you traverse through wiki:entry[1]. When you have multiple siblings with the same name, you can disambiguate the sibling you want by using the index notation. In JCR, indexing same-name siblings begins at 1, not 0. Furthermore, the indexing is based on the order in which nodes are returned in the iterator acquired through Node.getNodes().

You can then browse through all the JCRWiki entries by obtaining a NodeIterator that returns the children of a specific node, as illustrated in Listing 9:


Listing 9. Browsing a content repository

Node encyclopedia = rn.getNode("wiki:encyclopedia");
NodeIterator entries = encyclopedia.getNodes("wiki:entry");

while (entries.hasNext()) {

    Node entry = entries.nextNode();

    System.out.println(entry.getName());
    System.out.println(entry.getProperty("wiki:title").getString());
    System.out.println(entry.getProperty("wiki:content").getString());
    System.out.println(entry.getPath());

    Property category = entry.getProperty("wiki:category");

    try {
      String c = category.getValue().getString();
      System.out.println("Category: " + c);
    } catch (ValueFormatException e) {

      Value[] categories = category.getValues();

      for (Value c : categories) {
        System.out.println("Category: " + c.getString());
      }
    }		 
}

Because the category property can either be multivalued or single-valued, you check for it with a try-catch statement. If getValue() is called on a multivalued property, a ValueFormatException is thrown. In general, both direct and traversal access require knowledge of the internal node structure. Therefore, let's take a look at more expressive ways to access nodes, using search.

Searching content with XPath

As you've already seen, traversal and direct access require positional knowledge of the articles. A better way to obtain a specific entry is through the JCR's XPath search facility. Because the workspace model is a lot like an XML document in that it is a tree structure, XPath is an ideal syntax for finding nodes. XPath queries are performed through the QueryManager object. The process is similar to accessing records through JDBC, as you can see in Listing 10:


Listing 10. Searching content with XPath

QueryManager qm = ws.getQueryManager();
Query q = qm.createQuery
("//wiki:encyclopedia/wiki:entry[@wiki:title = 'rose']",
		 		 		 		 Query.XPATH);

QueryResult result = q.execute();
NodeIterator it = result.getNodes();

while (it.hasNext()) {
		 Node n = it.nextNode();

		 System.out.println(n.getName());
		 System.out.println(n.getProperty("wiki:title").getString());
		 System.out.println(n.getProperty("wiki:content").getString());
}

The second parameter to createQuery() specifies the query language to use. A JCR implementation may additionally choose to support Query.SQL for SQL syntax. You can also perform more complex queries. For example, you could query for all entries whose contents contain the word rose:

Query q = qm.createQuery
   ("//wiki:encyclopedia/" +
   "wiki:entry[jcr:contains(@wiki:content, 'rose')]",
   Query.XPATH);

Importing and exporting content with XML

JSR-170 has made efforts to ensure portability across JCR implementations. One of the ways it promotes this portability is through the use of its standardized XML importing and exporting features. Content from a compliant vendor repository can easily be transferred to another compliant vendor repository by using these facilities. Another advantage of using XML for serialization is that you can manipulate the exported repository with traditional XML parsing tools. You can perform an export with only the three lines of code in Listing 11:


Listing 11. Exporting data

File outputFile = new File("systemview.xml");
FileOutputStream out = new FileOutputStream(outputFile);
session.exportSystemView("/wiki:encyclopedia", out, false, false);

You can then transfer the resulting XML file to another fresh repository, as illustrated in Listing 12:


Listing 12. Transferring data

File inputFile = new File("systemview.xml");
FileInputStream in = new FileInputStream(inputFile);
session.importXML
		 ("/", in, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
session.save();

Adding binary content

Until now, you have been using StringValue for properties for nodes. But JCR also supports other types, including booleans, dates, and long integers. Listing 13 illustrates the storage of binary images in a node using the available stream type in JCR. In this listing, you add the file rose.gif as metadata to a nt:file node. The file data itself is stored as an nt:resource sub-child.


Listing 13. Adding binary content

File file = new File("rose.gif");
MimeTable mt = MimeTable.getDefaultTable();
String mimeType = mt.getContentTypeFor(file.getName());
if (mimeType == null) mimeType = "application/octet-stream";

Node fileNode = roseMode.addNode(file.getName(), "nt:file");
Node resNode = fileNode.addNode("jcr:content", "nt:resource");
resNode.setProperty("jcr:mimeType", mimeType);
resNode.setProperty("jcr:encoding", "");
resNode.setProperty("jcr:data", new FileInputStream(file));
Calendar lastModified = Calendar.getInstance();
lastModified.setTimeInMillis(file.lastModified());
resNode.setProperty("jcr:lastModified", lastModified);

After using the MimeTable class to determine the content type, you load the file using a FileInputStream. It is then simply a matter of adding the properly-named properties to an nt:resource node type, which holds the actual file data.

Versioning

JSR-170 supports many optional features, including access control, transactions, locking, and versioning. These features are full topics in and of themselves, so I must conclude by briefly touching only on the most popular of them: versioning. In the simplest case, you can perform versioning by adding a mix:versionable mixin type to any node. You can accomplish versioning on that node with a set of methods on the node that resemble CVS operations, outlined in Listing 14:


Listing 14. Versioning methods

n.checkout();
n.setProperty("wiki:content", "Updated content for the entry.");
n.save();
n.checkin();

Other operations available in JCR include updating, merging, and restoring previous versions. To browse the entire version history for a given node, walk through the steps in Listing 15:


Listing 15. Browsing version histories

VersionHistory vh = n.getVersionHistory();
VersionIterator vi = vh.getAllVersions();

vi.skip(1);
while (vi.hasNext()) {
		 Version v = vi.nextVersion();
		 NodeIterator ni = v.getNodes();

		 while (ni.hasNext()) {
		  Node nv = ni.nextNode();
		  System.out.println("Version: " +
		  v.getCreated().getTime());

		  System.out.println(nv.getProperty("wiki:title").getString());
		  System.out.println(nv.getProperty("wiki:content").getString());
		 }

}

Usingvi.skip(1) may seem peculiar at first, but its usage is clear when you see how version histories are stored internally, as illustrated in Figure 3:


Figure 3. Structural model of the version history for a node
Structural model of the version history for a node

In a version history for a node, each version is stored as a child of the version history, with references indicating successors of a version. In Figure 3, version Vb is a successor to version Va, and version Vc and Va are successors to Vroot, where the version graph begins. Vroot is an auto-created subnode that is the starting point for the version graph; it does not contain any state information. Hence, when the application iterates through the version history, Vroot is skipped.


Conclusion

This article provides you with a broad introduction to the many features offered by the JSR-170 specification. The final specification release, approved on June 17, 2005, has already brought about two commercial implementations: Day Software's CRX and Obinary's Magnolia Power Pack. The introduction of JSR-170 has also given rise to corporate open source portals and content management systems, such as Magnolia and the eXo platform. Most importantly, JSR-170 has strong support from industry leaders, including SAP AG, Macromedia, and IBM, establishing its use and importance in the enterprise landscape. Just as object-relationship mapping frameworks transformed database programming, the JCR API has the potential to dramatically change the way we think about and develop content applications.


Resources

Learn

Get products and technologies

  • Apache Jackrabbit: An open source implementation of the Content Repository for Java Technology API. It is published under the Apache License.

  • The JSR-170 Tools online Web portal: Here Day Software, the specification lead for JSR-170, demonstrates its commercial Content Repository Extreme (CRX) implementation of JCR. CRX offers level I and level II functionality, as well as advanced features such as versioning, locking, access control, and events. A free trial is available.

  • Maven: This is the software project build system used in the Apache Jackrabbit implementation.

  • Subversion: This popular version control system, published under an Apache-style license, offers a compelling replacement to CVS.

  • Xalan-Java: An XSLT processor for transforming XML documents into HTML, text, or other XML document types.

Discuss

About the author

Titus Barik is a content application developer with an interest in open source enterprise solutions. He has deployed Magnolia and other JSR-170 technologies successfully in corporate and non-profit environments. His personal weblog is available at barik.net, and he welcomes your comments and suggestions.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Architecture
ArticleID=92273
ArticleTitle=Introducing the Java Content Repository API
publish-date=06272006
author1-email=titus@barik.net
author1-email-cc=