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();
}
}
}
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:
/hello/world
Hello, World!
The basic structure of this example application is the same as in the First
Hop example, so let's just walk through the differences:
import javax.jcr.SimpleCredentials;
import javax.jcr.Node;
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.
new SimpleCredentials("username", "password".toCharArray())
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 Repository.login(Credentials credentials) method.
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.
Node root = session.getRootNode();
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.
Node hello = root.addNode("hello");
Node world = hello.addNode("world");
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.
world.setProperty("message", "Hello, World!");
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.
session.save();
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.
Node node = root.getNode("hello/world");
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.
System.out.println(node.getPath());
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".
System.out.println(node.getProperty("message").getString());
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.
root.getNode("hello").remove();
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.
## 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:
Three Namespaces
An Ellipse and a Rectangle
The equation for ellipses
1
x
2
a
2
y
2
b
2
Last Modified January 10, 2002
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.
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());
}
}
}
Running the ThirdHop class should produce output like the following:
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
.
.
.