jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From conflue...@apache.org
Subject [CONF] Apache Jackrabbit: First Hops (page edited)
Date Mon, 02 Jun 2008 13:59:00 GMT
First Hops (JCR) edited by Alexander Klimetschek
      Page: http://cwiki.apache.org/confluence/display/JCR/First+Hops
   Changes: http://cwiki.apache.org/confluence/pages/diffpagesbyversion.action?pageId=75343&originalVersion=1&revisedVersion=2






Content:
---------------------------------------------------------------------

Welcome to your first hops into the world of Jackrabbit! This introduction gives you a hands-on
experience with Jackrabbit and the JCR API. Once you have finished hopping through this document,
you should be all set to continue on your own with the official JCR specification and the
documentation on this site.

h2. Hop 0: Getting started

Before you can start using Jackrabbit, you need to have it installed on your computer. You
can do this either by downloading a binary release and all the required dependencies or by
building the Jackrabbit sources.

Once you have Jackrabbit available locally, you should make sure that you have at least version
1.4 of the Java 2 Platform, Standard Edition (J2SE) installed and the following libraries
configured in your Java classpath:

* jcr-1.0.jar
* jackrabbit-api-1.4.jar
* jackrabbit-core-1.4.jar
* jackrabbit-jcr-commons-1.4.jar
* jackrabbit-spi-1.4.jar
* jackrabbit-spi-commons-1.4.jar
* jackrabbit-text-extractors-1.4.jar
* slf4j-api-1.3.0.jar
* slf4j-simple-1.3.0.jar
* commons-collections-3.1.jar
* lucene-core-2.2.0.jar
* derby-10.2.1.6.jar
* concurrent-1.3.4.jar

You find the jcr-1.0.jar either as part of the download package of the JSR-170 (JCR 1.0) specification
at http://jcp.org/aboutJava/communityprocess/final/jsr170/index.html or directly on the public
Maven 2 repository at http://repo1.maven.org/maven2/javax/jcr/jcr/1.0/. The Jackrabbit jars
can be found on the [downloads] page.

You should also add the current directory in your classpath to make it easy to compile and
run the example classes.

HINT: You probably have an error in your classpath if you get a ClassNotFoundException message
when trying to compile or run the examples below.

h2. Hop 1: Logging in to Jackrabbit

Once you are done with the setup tasks, we can start doing some real work. As a warm-up we'll
create a Jackrabbit content repository and start a login session for accessing it. The full
example application that does this is shown below, with line-by-line explanations following
shortly after.

{code:title=FirstHop.java}
import javax.jcr.Repository;
import javax.jcr.Session;
import org.apache.jackrabbit.core.TransientRepository;

/**
 * First hop example. Logs in to a content repository and prints a
 * status message.
 */
public class FirstHop {

    /**
     * The main entry point of the example application.
     *
     * @param args command line arguments (ignored)
     * @throws Exception if an error occurs
     */
    public static void main(String[] args) throws Exception {
        Repository repository = new TransientRepository();
        Session session = repository.login();
        try {
            String user = session.getUserID();
            String name = repository.getDescriptor(Repository.REP_NAME_DESC);
            System.out.println(
                    "Logged in as " + user + " to a " + name + " repository.");
        } finally {
            session.logout();
        }
    }

}
{code}

You can also download the source file as FirstHop.java. If you have your classpath set up,
you can compile the application with javac FirstHop.java and run it with java FirstHop to
get the following output.

{code}
Logged in as anonymous to a Jackrabbit repository.
{code}

In addition to producing the above status line the application copies a default repository
configuration file to repository.xml and creates an initial Jackrabbit content repository
in the repository subdirectory. You can use the system properties org.apache.jackrabbit.repository.conf
and org.apache.jackrabbit.repository.home to set alternative configuration file and repository
directory locations.

Read on for a detailed breakdown of the FirstHop application:

{code}
import javax.jcr.Repository;
import javax.jcr.Session;
{code}

The JCR API interfaces are located in the javax.jcr package found in the jcr-1.0.jar library.
The promise of the JCR API is that if you only use these interfaces in your content application,
it should remain mostly independent of the underlying content repository implementation.

The Repository interface represents a given content repository instance and the Session interface
represents a single login session for accessing the repository. A session is needed to access
any content within a repository.

Note that a Session instance is not guaranteed to be thread-safe so you should start multiple
sessions if you need to access repository content simultaneously from different threads. This
is especially important for things like web applications.

{code}
import org.apache.jackrabbit.core.TransientRepository;
{code}

The best practice for deploying Jackrabbit is to use JNDI or some other configuration mechanism
in a container environment to keep the application code free of direct Jackrabbit dependencies,
but since we are creating a simple standalone application we can take a shortcut by using
the TransientRepository class from Jackrabbit core.

{code}
public class FirstHop
public static void main(String[] args) throws Exception
{code}

The FirstHop example is a simple standalone application that fits nicely in the main() method
and lets the JVM take care of the possible exceptions. More substantial content applications
could also be written as web application or EJB components with different setup and error
handling patterns.

{code}
Repository repository = new TransientRepository();
{code}

The TransientRepository class implements the JCR Repository interface, so you can simply assign
a TransientRepository instance to a Repository variable. The default constructor contains
a utility feature that will take care of the initial configuration and repository construction
when the first session is started. Thus there is no need for manual configuration for now
unless you want direct control over the repository setup.

The TransientRepository implementation will automatically initialize the content repository
when the first session is started and shut it down when the last session is closed. Thus there
is no need for explicit repository shutdown as long as all sessions are properly closed. Note
that a Jackrabbit repository directory contains a lock file that prevents it from being accessed
simultaneously by multiple processes. You will see repository startup exceptions caused by
the lock file if you fail to properly close all sessions or otherwise shut down the repository
before leaving the process that accesses a repository. Normally you can just manually remove
the lock file in such cases but such cases always present a chance of repository corruption
especially if you use a non-transactional persistence manager.

{code}
Session session = repository.login();
{code}

The default Repository.login() method starts a repository session using the default workspace
and no user credentials. Jackrabbit tries to use the Java Authentication and Authorization
Service (JAAS) configuration in such cases, but defaults to the anonymous user if a JAAS Subject
is not found.

Since we use the TransientRepository class as the Repository implementation, this step will
also cause the repository to be initialized.

{code}
try { ... } finally { session.logout(); }
{code}

It is a good practice to properly release all acquired resources, and the JCR sessions are
no exception. The try-finally idiom is a good way to ensure that a resource really gets released,
as the release method gets called even if the intervening code throws an exception or otherwise
jumps outside the scope (for example using a return, break, or continue statement).

The Session.logout() method in the finally branch closes the session and since this is the
only session we have started, the TransientRepository is automatically shut down.

{code}
String user = session.getUserID();
{code}

The username or identifier of the user associated with a session is available using the Session.getUserID()
method. Jackrabbit returns "anonymous" by default.

{code}
String name = repository.getDescriptor(Repository.REP_NAME_DESC);
{code}

Each content repository implementation publishes a number of string descriptors that describe
the various implementation properties, like the implementation level and the supported optional
JCR features. See the Repository interface for a list of the standard repository descriptors.
The REP_NAME_DESC descriptor contains the name of the repository implementation, in this case
"Jackrabbit".

h2. Hop 2: Working with content

The main function of a content repository is allow applications to store and retrieve content.
The content in a JCR content repository consists of structured or unstructured data modeled
as a hierarchy of nodes with properties that contain the actual data.

The following example application first stores some content to the initially empty content
repository, then retrieves the stored content and outputs it, and finally removes the stored
content.

{code:title=SecondHop.java}
import javax.jcr.Repository;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.Node;
import org.apache.jackrabbit.core.TransientRepository;

/**
 * Second hop example. Stores, retrieves, and removes example content.
 */
public class SecondHop {

    /**
     * The main entry point of the example application.
     *
     * @param args command line arguments (ignored)
     * @throws Exception if an error occurs
     */
    public static void main(String[] args) throws Exception {
        Repository repository = new TransientRepository();
        Session session = repository.login(
                new SimpleCredentials("username", "password".toCharArray()));
        try {
            Node root = session.getRootNode();

            // Store content
            Node hello = root.addNode("hello");
            Node world = hello.addNode("world");
            world.setProperty("message", "Hello, World!");
            session.save();

            // Retrieve content
            Node node = root.getNode("hello/world");
            System.out.println(node.getPath());
            System.out.println(node.getProperty("message").getString());

            // Remove content
            root.getNode("hello").remove();
            session.save();
        } finally {
            session.logout();
        }
    }

}
{code}

Like in the first hop, this example source is also available as SecondHop.java. You can also
compile and run this class just like you did in the first hop example. Running this example
should produce the following output:

{code}
/hello/world
Hello, World!
{code}

The basic structure of this example application is the same as in the First Hop example, so
let's just walk through the differences:

{code}
import javax.jcr.SimpleCredentials;
import javax.jcr.Node;
{code}

These are two new classes we need for this example. The SimpleCredentials class is a simple
implementation of the Credentials interface used for passing explicit user credentials to
the Repository.login(Credentials) method.

The Node interface is used to manage the content nodes in a repository. There is a related
interface called Property for managing content properties, but in this example we use the
Property interface only indirectly.

{code}
new SimpleCredentials("username", "password".toCharArray())
{code}

As discussed in the First Hop example, the default Repository.login() method returns an anonymous
read-only session in the Jackrabbit default configuration. To be able to store and remove
content we need to create a session with write access, and to do that we need to pass some
credentials to the 

{code}
Repository.login(Credentials credentials) method.
{code}

The default Jackrabbit login mechanism accepts any username and password as valid credentials
and returns a session with full write access. Thus we only need to construct and use a SimpleCredentials
instance with some dummy username and password, in this case "username" and "password".

The SimpleCredentials constructor follows the JAAS convention of represenenting the username
as a normal String, but the password as a character array, so we need to use the String.toCharArray()
method to satisfy the constructor.

{code}
Node root = session.getRootNode();
{code}

Each JCR session is associated with a workspace that contains a single node tree. A simple
way to access the root node is to call the Session.getRootNode() method. Having a reference
to the root node allows us to easily store and retrieve content in the current workspace.

{code}
Node hello = root.addNode("hello");
Node world = hello.addNode("world");
{code}

New content nodes can be added using the Node.addNode(String relPath) method. The method takes
the name (or relative path) of the node to be added and creates the named node in the transient
storage associated with the current session. Until the transient storage is persisted, the
added node is only visible within the current session and not within any other session that
is concurrently accessing the content repository.

This code snippet creates two new nodes, called "hello" and "world", with "hello" being a
child of the root node and "world" a child of the "hello" node.

{code}
world.setProperty("message", "Hello, World!");
{code}

To add some content to the structure created using the "hello" and "world" nodes, we use the
Node.setProperty(String name, String value) method to add a string property called "message"
to the "world" node. The value of the property is the string "Hello, World!".

Like the added nodes, also the property is first created in the transient storage associated
with the current session. If the named property already exists, then this method will change
the value of that property.

{code}
session.save();
{code}

Even though the rest of our example would work just fine using only the transient storage
of the single session, we'd like to persist the changes we've made so far. This way other
sessions could also access the example content we just created. If you like, you could even
split the example application into three pieces for respectively storing, retrieving, and
removing the example content. Such a split would not work unless we persisted the changes
we make.

The Session.save() method persists all pending changes in the transient storage. The changes
are written to the persistent repository storage and they become visible to all sessions accessing
the same workspace. Without this call all changes will be lost forever when the session is
closed.

{code}
Node node = root.getNode("hello/world");
{code}

Since we are still using the same session, we could use the existing hello and world node
references to access the stored content, but let's pretend that we've started another session
and want to retrieve the content that was previously stored.

The Node.getNode(String relPath) method returns a reference to the node at the given path
relative to this node. The path syntax follows common file system conventions: a forward slash
separates node names, a single dot represents the current node, and a double dot the parent
node. Thus the path "hello/world" identifies the "world" child node of the "hello" child node
of the current node - in this case the root node. The end result is that the method returns
a node instance that represents the same content node as the world instance created a few
lines earlier.

{code}
System.out.println(node.getPath());
{code}

Each content node and property is uniquely identified by its absolute path within the workspace.
The absolute path starts with a forward slash and contains all the names of the ancestor nodes
in order before the name of the current node or property.

The path of a node or property can be retrieved using the Item.getPath() method. The Item
inteface is a superinterface of Node and Property, and contains all the functionality shared
by nodes and properties.

The node variable references the "world" node, so this statement will output the line "/hello/world".

{code}
System.out.println(node.getProperty("message").getString());
{code}

Properties can be accessed using the Node.getProperty(String relPath) method that returns
an instance of the Property interface that represents the property at the given path relative
to the current node. In this case the "message" property is the one we created a few lines
earlier.

A JCR property can contain either a single or multiple values of a given type. There are property
types for storing strings, numbers, dates, binary streams, node references, etc. We just want
the single string value, so we use the Property.getString() method. The result of this statement
is the line "Hello, World!" being outputted.

{code}
root.getNode("hello").remove();
{code}

Nodes and properties can be removed using the Item.remove() method. The method removes the
entire content subtree, so we only need to remove the topmost "hello" node to get rid of all
the content we added before.

Removals are first stored in the session-local transient storage, just like added and changed
content. Like before, the transient changes need to be explicitly saved for the content to
be removed from the persistent storage.

h2. Hop 3: Importing content

TODO: Update to match the style of previous hops.

To add content a bit more efficiently, you may want to try JCR's import facilities, such as
Session.importXML. The following XML document by Elliotte Rusty Harold provides an interesting
example that demonstrates a repository's namespace capabilities:

{code:title=test.xml}
<xhtml:html xmlns:xhtml="http://www.w3.org/1999/xhtml"
            xmlns:mathml="http://www.w3.org/1998/Math/MathML">
  <xhtml:head><xhtml:title>Three Namespaces</xhtml:title></xhtml:head>
  <xhtml:body>
    <xhtml:h1 align="center">An Ellipse and a Rectangle</xhtml:h1>
    <svg:svg xmlns:svg="http://www.w3.org/2000/svg" 
             width="12cm" height="10cm">
      <svg:ellipse rx="110" ry="130" />
      <svg:rect x="4cm" y="1cm" width="3cm" height="6cm" />
    </svg:svg>
    <xhtml:p>The equation for ellipses</xhtml:p>
<mathml:math>
  <mathml:apply>
    <mathml:eq/>
    <mathml:cn> 1 </mathml:cn>
    <mathml:apply>
      <mathml:plus/>
      <mathml:apply>
        <mathml:divide/>
        <mathml:apply>
          <mathml:power/>
          <mathml:ci> x </mathml:ci>
          <mathml:cn> 2 </mathml:cn>
        </mathml:apply>
        <mathml:apply>
          <mathml:power/>
          <mathml:ci> a </mathml:ci>
          <mathml:cn> 2 </mathml:cn>
        </mathml:apply>
      </mathml:apply>
      <mathml:apply>
        <mathml:divide/>
        <mathml:apply>
          <mathml:power/>
          <mathml:ci> y </mathml:ci>
          <mathml:cn> 2 </mathml:cn>
        </mathml:apply>
        <mathml:apply>
          <mathml:power/>
          <mathml:ci> b </mathml:ci>
          <mathml:cn> 2 </mathml:cn>
        </mathml:apply>        
      </mathml:apply>
    </mathml:apply>
 </mathml:apply>
</mathml:math>
    <xhtml:hr/>
    <xhtml:p>Last Modified January 10, 2002</xhtml:p>    
  </xhtml:body>
</xhtml:html>
{code}

The third example application shown below will import the XML file called test.xml from the
current directory into a new content repository node called importxml. Once the XML content
is imported, the application recursively dumps the contents of the entire workspace using
the simple dump() method.

{code:title=ThirdHop.java}
import javax.jcr.*;
import org.apache.jackrabbit.core.TransientRepository;
import java.io.FileInputStream;

/**
 * Third Jackrabbit example application. Imports an example XML file
 * and outputs the contents of the entire workspace.
 */
public class ThirdHop {

    /** Runs the ThirdHop example. */
    public static void main(String[] args) throws Exception {
        // Set up a Jackrabbit repository with the specified
        // configuration file and repository directory
        Repository repository = new TransientRepository();

        // Login to the default workspace as a dummy user
        Session session = repository.login(
            new SimpleCredentials("username", "password".toCharArray()));
        try {
            // Use the root node as a starting point
            Node root = session.getRootNode();

            // Import the XML file unless already imported
            if (!root.hasNode("importxml")) {
                System.out.print("Importing xml... ");
                // Create an unstructured node under which to import the XML
                Node node = root.addNode("importxml", "nt:unstructured");
                // Import the file "test.xml" under the created node
                FileInputStream xml = new FileInputStream("test.xml");
                session.importXML(
                    "/importxml", xml, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
                xml.close();
                // Save the changes to the repository
                session.save();
                System.out.println("done.");
            }

            dump(root);
        } finally {
            session.logout();
        }
    }

    /** Recursively outputs the contents of the given node. */
    private static void dump(Node node) throws RepositoryException {
        // First output the node path
        System.out.println(node.getPath());
        // Skip the virtual (and large!) jcr:system subtree
        if (node.getName().equals("jcr:system")) {
            return;
        }

        // Then output the properties
        PropertyIterator properties = node.getProperties();
        while (properties.hasNext()) {
            Property property = properties.nextProperty();
            if (property.getDefinition().isMultiple()) {
                // A multi-valued property, print all values
                Value[] values = property.getValues();
                for (int i = 0; i < values.length; i++) {
                    System.out.println(
                        property.getPath() + " = " + values[i].getString());
                }
            } else {
                // A single-valued property
                System.out.println(
                    property.getPath() + " = " + property.getString());
            }
        }

        // Finally output all the child nodes recursively
        NodeIterator nodes = node.getNodes();
        while (nodes.hasNext()) {
            dump(nodes.nextNode());
        }
    }

}
{code}

Running the ThirdHop class should produce output like the following:

{code}
Importing XML... done.
/
/jcr:primaryType=rep:root
/jcr:system
/testnode
/testnode/jcr:primaryType=nt:unstructured
/testnode/testprop=Hello, World.
/importxml
/importxml/jcr:primaryType=nt:unstructured
/importxml/xhtml:html
/importxml/xhtml:html/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:head
/importxml/xhtml:html/xhtml:head/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:head/xhtml:title
/importxml/xhtml:html/xhtml:head/xhtml:title/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:head/xhtml:title/jcr:xmltext
/importxml/xhtml:html/xhtml:head/xhtml:title/jcr:xmltext/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:head/xhtml:title/jcr:xmltext/jcr:xmlcharacters=Three Namespaces
/importxml/xhtml:html/xhtml:body
/importxml/xhtml:html/xhtml:body/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:body/xhtml:h1
/importxml/xhtml:html/xhtml:body/xhtml:h1/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:body/xhtml:h1/align=center
/importxml/xhtml:html/xhtml:body/xhtml:h1/jcr:xmltext
/importxml/xhtml:html/xhtml:body/xhtml:h1/jcr:xmltext/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:body/xhtml:h1/jcr:xmltext/jcr:xmlcharacters=An Ellipse and a Rectangle
/importxml/xhtml:html/xhtml:body/svg:svg
/importxml/xhtml:html/xhtml:body/svg:svg/jcr:primaryType=nt:unstructured
/importxml/xhtml:html/xhtml:body/svg:svg/width=12cm
/importxml/xhtml:html/xhtml:body/svg:svg/height=10cm
.
.
.
{code}


---------------------------------------------------------------------
CONFLUENCE INFORMATION
This message is automatically generated by Confluence

Unsubscribe or edit your notifications preferences
   http://cwiki.apache.org/confluence/users/viewnotifications.action

If you think it was sent incorrectly contact one of the administrators
   http://cwiki.apache.org/confluence/administrators.action

If you want more information on Confluence, or have a bug to report see
   http://www.atlassian.com/software/confluence



Mime
View raw message