JBoss.orgCommunity Documentation
Using ModeShape within your application is actually quite straightforward. As you'll see in this chapter,
the first step is setting up ModeShape and starting the JcrEngine
. After that, you obtain the
javax.jcr.Repository
instance for a named repository and just use the standard JCR API throughout your
application.
ModeShape encapsulates everything necessary to run one or more JCR repositories into a single JcrEngine
instance.
This includes all underlying repository sources, the pools of connections to the sources, the sequencers,
the MIME type detector(s), and the Repository implementations.
Obtaining a JcrEngine
instance is very easy - assuming that you have a valid JcrConfiguration
instance. We'll see
how to get one of those in a little bit, but if you have one then all you have to do is build and start the engine:
JcrConfiguration config = ...
JcrEngine engine = config.build();
engine.start();
Obtaining a JCR Repository instance is a matter of simply asking the engine for it by the name defined in the configuration:
javax.jcr.Repository repository = engine.getRepository("Name of repository");
At this point, your application can proceed by working with the JCR API.
And, once you're finished with the JcrEngine
, you should shut it down:
engine.shutdown();
engine.awaitTermination(3,TimeUnit.SECONDS); // optional
When the shutdown()
method is called, the Repository instances managed by the engine are marked as being shut down,
and they will not be able to create new Sessions. However, any existing Sessions or ongoing operations (e.g., event notifications)
present at the time of the shutdown()
call will be allowed to finish.
In essence, shutdown()
is a graceful request, and since it may take some time to complete,
you can wait until the shutdown has completed by simply calling awaitTermination(...)
as shown above.
This method will block until the engine has indeed shutdown or until the supplied time duration has passed (whichever comes first).
And, yes, you can call the awaitTermination(...)
method repeatedly if needed.
The previous section assumed the existence of a JcrConfiguration
. It's not really that creating an instance is all that difficult.
In fact, there's only one no-argument constructor, so actually creating the instance is a piece of cake. What can be a little more challenging,
though, is setting up the JcrConfiguration
instance, which must define the following components:
Repository sources
are the POJO objects that each describe a particular
location where content is stored. Each repository source object is an instance of a ModeShape connector, and is configured
with the properties that particular source. ModeShape's RepositorySource classes are analogous to JDBC's DataSource
classes -
they are implemented by specific connectors (aka, "drivers") for specific kinds of repository sources (aka, "databases").
Similarly, a RepositorySource instance is analogous to a DataSource
instance, with bean properties for each configurable
parameter. Therefore, each repository source definition must supply the name of the RepositorySource class, any
bean properties, and, optionally, the classpath that should be used to load the class.
Repositories
define the JCR repositories that are available. Each
repository has a unique name that is used to obtain the Repository instance from the JcrEngine
's getRepository(String)
method, but each repository definition also can include the predefined namespaces (other than those automatically defined by
ModeShape), various options, and the node types that are to be available in the repository without explicit registration
through the JCR API.
Sequencers
define the particular sequencers that are available for use.
Each sequencer definition provides the path expressions governing which nodes in the repository should be sequenced when those nodes change,
and where the resulting output generated by the sequencer should be placed. The definition also must state the name of
the sequencer class, any bean properties and, optionally, the classpath that should be used to load the class.
MIME type detectors
define the particular MIME type detector(s) that should
be made available. A MIME type detector does exactly what the name implies: it attempts to determine the MIME type given a
"filename" and contents. ModeShape automatically uses a detector that uses the file extension to identify the MIME type,
but also provides an implementation that uses an external library to identify the MIME type based upon the contents.
The definition must state the name of the detector class, any bean properties and, optionally, the classpath that should
be used to load the class.
There really are three options:
Load from a file
is conceptually the easiest and requires the least amount
of Java code, but it now requires a configuration file.
Load from a configuration repository
is not much more complicated than loading
from a file, but it does allow multiple JcrEngine
instances (usually in different processes perhaps on different machines)
to easily access their (shared) configuration. And technically, loading the configuration from a file really just creates an
InMemoryRepositorySource
, imports the configuration file into that source, and then proceeds with this approach.
Programmatic configuration
is always possible, even if the configuration is loaded
from a file or repository. Using the JcrConfiguration
's API, you can define (or update or remove) all of the definitions that make
up a configuration.
Each of these approaches has their obvious advantages, so the choice of which one to use is entirely up to you.
Loading the ModeShape configuration from a file is actually very simple:
JcrConfiguration config = new JcrConfiguration();
configuration.loadFrom(file);
where the file
parameter can actually be a File
instance, a URL
to the file, an InputStream
containing the contents of the file, or even a String containing the contents of the file.
The loadFrom(...)
method can be called any number of times, but each time it is called it completely wipes
out any current notion of the configuration and replaces it with the configuration found in the file.
There is an optional second parameter that defines the Path within the configuration file identifying the parent node of the various configuration nodes. If not specified, it assumes "/". This makes it possible for the configuration content to be located at a different location in the hierarchical structure. (This is not often required, but when it is required this second parameter is very useful.)
Here is the configuration file that is used in the repository example, though it has been simplified a bit and most comments have been removed for clarity):
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns:mode="http://www.modeshape.org/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0">
<!--
Define the JCR repositories
-->
<mode:repositories>
<!--
Define a JCR repository that accesses the 'Cars' source directly.
This of course is optional, since we could access the same content through 'vehicles'.
-->
<mode:repository jcr:name="car repository" mode:source="Cars">
<mode:options jcr:primaryType="mode:options">
<jaasLoginConfigName jcr:primaryType="mode:option" mode:value="modeshape-jcr"/>
</mode:options>
<mode:descriptors>
<!--
This adds a JCR Repository descriptor named "myDescriptor" with a value of "foo".
So this code:
Repository repo = ...;
System.out.println(repo.getDescriptor("myDescriptor");
Will now print out "foo".
-->
<myDescriptor mode:value="foo" />
</mode:descriptors>
<!--
Import the custom node types defined in the named resource (a file at a
classpath-relative path). If there was more than one file with custom node
types, we could either add successive <jcr:nodeTypes ... /> elements or just
add all of the files as a comma-delimited string in the mode:resource property.
-->
<jcr:nodeTypes mode:resource="/tck/tck_test_types.cnd" />
</mode:repository>
</mode:repositories>
<!--
Define the sources for the content. These sources are directly accessible using the
ModeShape-specific Graph API.
-->
<mode:sources jcr:primaryType="nt:unstructured">
<mode:source jcr:name="Cars"
mode:classname="org.modeshape.graph.connector.inmemory.InMemoryRepositorySource"
mode:retryLimit="3" mode:defaultWorkspaceName="workspace1"/>
<mode:source jcr:name="Aircraft"
mode:classname="org.modeshape.graph.connector.inmemory.InMemoryRepositorySource">
<!-- Define the name of the workspace used by default. Optional, but convenient. -->
<defaultWorkspaceName>workspace2</defaultWorkspaceName>
</mode:source>
</mode:sources>
<!--
Define the sequencers. This is an optional section. For this example, we're not using any sequencers.
-->
<mode:sequencers>
<!--mode:sequencer jcr:name="Image Sequencer">
<mode:classname>
org.modeshape.sequencer.image.ImageMetadataSequencer
</mode:classname>
<mode:description>Image metadata sequencer</mode:description>
<mode:pathExpression>/foo/source => /foo/target</mode:pathExpression>
<mode:pathExpression>/bar/source => /bar/target</mode:pathExpression>
</mode:sequencer-->
</mode:sequencers>
<mode:mimeTypeDetectors>
<mode:mimeTypeDetector jcr:name="Detector"
mode:description="Standard extension-based MIME type detector"/>
</mode:mimeTypeDetectors>
</configuration>
Loading the ModeShape configuration from an existing repository is also pretty straightforward. Simply create and configure the
RepositorySource instance to point to the desired repository, and then call the loadFrom(RepositorySource source)
method:
RepositorySource configSource = ...
JcrConfiguration config = new JcrConfiguration();
configuration.loadFrom(configSource);
This really is a more advanced way to define your configuration, so we won't go into how you configure a RepositorySource.
The loadFrom(...)
method can be called any number of times, but each time it is called it completely wipes
out any current notion of the configuration and replaces it with the configuration found in the file.
There is an optional second parameter that defines the name of the workspace in the supplied source where the configuration content can be found. It is not needed if the workspace is the source's default workspace. There is an optional third parameter that defines the Path within the configuration repository identifying the parent node of the various configuration nodes. If not specified, it assumes "/". This makes it possible for the configuration content to be located at a different location in the hierarchical structure. (This is not often required, but when it is required this second parameter is very useful.)
Defining the configuration programmatically is not terribly complicated, and it for obvious reasons results in more verbose Java code. But this approach is very useful and often the easiest approach when the configuration must change or is a reflection of other dynamic information.
The JcrConfiguration
class was designed to have an easy-to-use API that makes it easy to configure each of the different kinds of
components, especially when using an IDE with code completion. Here are several examples:
Each repository source definition must include the name of the RepositorySource class as well as each bean property that should be set on the object:
JcrConfiguration config = ...
config.repositorySource("source A")
.usingClass(InMemoryRepositorySource.class)
.setDescription("The repository for our content")
.setProperty("defaultWorkspaceName", workspaceName);
This example defines an in-memory source with the name "source A", a description, and a single "defaultWorkspaceName" bean property. Different RepositorySource implementations will the bean properties that are required and optional. Of course, the class can be specified as Class reference or a string (followed by whether the class should be loaded from the classpath or from a specific classpath).
Each time repositorySource(String)
is called, it will either load the existing definition with the supplied
name or will create a new definition if one does not already exist. To remove a definition, simply call remove()
on the result of repositorySource(String)
.
The set of existing definitions can be accessed with the repositorySources()
method.
Each repository must be defined to use a named repository source, but all other aspects (e.g., namespaces, node types, options) are optional.
JcrConfiguration config = ...
config.repository("repository A")
.addNodeTypes("myCustomNodeTypes.cnd")
.setSource("source 1")
.registerNamespace("acme","http://www.example.com/acme")
.setOption(JcrRepository.Option.JAAS_LOGIN_CONFIG_NAME, "modeshape-jcr");
This example defines a repository that uses the "source 1" repository source (which could be a federated source, an in-memory source, a database store, or any other source). Additionally, this example adds the node types in the "myCustomNodeTypes.cnd" file as those that will be made available when the repository is accessed. It also defines the "http://www.example.com/acme" namespace, and finally sets the "JAAS_LOGIN_CONFIG_NAME" option to define the name of the JAAS login configuration that should be used by the ModeShape repository.
Each time repository(String)
is called, it will either load the existing definition with the supplied
name or will create a new definition if one does not already exist. To remove a definition, simply call remove()
on the result of repository(String)
.
The set of existing definitions can be accessed with the repositories()
method.
Each defined sequencer must specify the name of the StreamSequencer implementation class as well as the path expressions defining which nodes should be sequenced and the output paths defining where the sequencer output should be placed (often as a function of the input path expression).
JcrConfiguration config = ...
config.sequencer("Image Sequencer")
.usingClass("org.modeshape.sequencer.image.ImageMetadataSequencer")
.loadedFromClasspath()
.setDescription("Sequences image files to extract the characteristics of the image")
.sequencingFrom("//(*.(jpg|jpeg|gif|bmp|pcx|png|iff|ras|pbm|pgm|ppm|psd)[*])/jcr:content[@jcr:data]")
.andOutputtingTo("/images/$1");
This shows an example of a sequencer definition named "Image Sequencer" that uses the ImageMetadataSequencer
class
(loaded from the classpath), that is to sequence the "jcr:data" property on any new or changed nodes that are named
"jcr:content" below a parent node with a name ending in ".jpg", ".jpeg", ".gif", ".bmp", ".pcx", ".iff", ".ras",
".pbm", ".pgm", ".ppm" or ".psd". The output of the sequencing operation should be placed at the "/images/$1" node,
where the "$1" value is captured as the name of the parent node. (The capture groups work the same way as regular expressions.)
Of course, the class can be specified as Class reference or a string (followed by whether the class should be loaded from
the classpath or from a specific classpath).
Each time sequencer(String)
is called, it will either load the existing definition with the supplied
name or will create a new definition if one does not already exist. To remove a definition, simply call remove()
on the result of sequencer(String)
.
The set of existing definitions can be accessed with the sequencers()
method.
Note that in addition to including a description for the configuration, it is also possible to set sequencer-specific properties
using the setProperty(String,String[])
method. When ModeShape uses this configuration to set up a sequencing
operation, it will instantiate the StreamSequencer class and will call a JavaBean-style setter method for each property.
For example, calling setProperty("foo","val1")
on the sequencer configuration will mean that ModeShape
will instantiate the sequencer implementation and will look for a setFoo(String)
method on the sequencer implementation
class, and use that method (if found) to pass the "val1" value to the instance.
Each defined MIME type detector must specify the name of the MimeTypeDetector implementation class as well as any other bean properties required by the implementation.
JcrConfiguration config = ...
config.mimeTypeDetector("Extension Detector")
.usingClass(org.modeshape.graph.mimetype.ExtensionBasedMimeTypeDetector.class);
Of course, the class can be specified as Class reference or a string (followed by whether the class should be loaded from the classpath or from a specific classpath).
Each time mimeTypeDetector(String)
is called, it will either load the existing definition with the supplied
name or will create a new definition if one does not already exist. To remove a definition, simply call remove()
on the result of mimeTypeDetector(String)
.
The set of existing definitions can be accessed with the mimeTypeDetectors()
method.
Regardless of how the JcrConfiguration
is loaded, it can also be stored to a file or stream in an XML format that can then be
reloaded in the future to recreate the configuration. This makes it very easy to programmatically generate a configuration file
once while being able to load that same configuration at a later time (or on a different instance).
JcrConfiguration config = ...
String pathToFile = ...
// Save any changes before this point in the configuration repository ...
configuration.save();
// And now write out the configuration repository to a file ...
configuration.storeTo(pathToFile);
This will create a file at pathToFile
that contains the current configuration in XML format. Any changes made after
the most recent call to the save()
method on the JcrConfiguration
object will not be saved in the configuration
repository, and thus will not be in the generated file. The generated XML will not be formatted to maximize human readability.
Sometimes your applications can simply define a JcrConfiguration
and instantiate the JcrEngine
instance directly.
This is very straightforward, and this is what the ModeShape examples do.
Web applications are a different story. Often, you may not want your web application to contain the code that initializes
a ModeShape engine. Or, you may want the same JcrEngine
instance to be reused in multiple web applications deployed
to the same web/application server. In these cases, it is possible to configure the web/app server's JNDI instance to
instantiate the JcrEngine
, meaning the web applications need only use the standard JNDI and JCR APIs.
Here's an example of how such a web application would obtain a JCR Repository instance, use it to create a JcrSession
,
and then close the session when completed.
Session session = null;
try {
// Look up the JCR Repository object ...
InitialContext initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
Repository repo = (Repository) envCtx.lookup("jcr/local"); // name in JNDI is defined by configuration
// Obtain a JCR Session using simple authentication
// (or use anonymous authentication if desired)
session = repo.login(new SimpleCredentials("username", "password".toCharArray()));
// Use the JCR Session to do something interesting
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (session != null) session.logout();
}
Note that the location of the Repository instance in JNDI depends upon the configuration. In this example, we used
"jcr/local
", but the only requirement is that it match the location where it was placed in JNDI.
We showed how web applications can use an existing Repository instance. In the next section, we describe how to configure the web server so that the Repository instance is available in JNDI.
Each kind of web server or application server is different, but all servlet containers do provide a way of configuring
objects and placing them into JNDI. ModeShape provides a JndiRepositoryFactory
class that implements
and that can be used in the server's configuration. The JndiRepositoryFactory
requires two properties:
configFile
is the path to the
configuration file resource, which must be available on the classpath
repositoryName
is the name of a JCR repository that exists
in the JcrConfiguration
and that will be made available by this JNDI entry
Here's an example of a fragment of the conf/context.xml
for Tomcat:
<Resource name="jcr/local"
auth="Container"
type="javax.jcr.Repository"
factory="org.modeshape.jcr.JndiRepositoryFactory"
configFile="/resource/path/to/configuration.xml"
repositoryName="Test Repository Source" />
Note that it is possible to have multiple Resource
entries. The JndiRepositoryFactory
ensures
that only one JcrEngine
is instantiated, but that a Repository instance is registered for each entry.
Before the server can start, however, all of the ModeShape jars need to be placed on the classpath for the server. JAAS also needs to be configured, and this can be done using the application server's configuration or in your web application if you're using a simple servlet container.
The ModeShape community has solicited input on how we can make it easier to consume and use ModeShape in applications that do not use Maven. Check out the discussion thread, and please add any suggestions or opinions!
Then, your web application needs to reference the Resource
and state its requirements in its
web.xml
:
<resource-env-ref>
<description>Repository</description>
<resource-env-ref-name>jcr/local</resource-env-ref-name>
<resource-env-ref-type>javax.jcr.Repository</resource-env-ref-type>
</resource-env-ref>
Note that the value of resource-env-ref-name
matches the value of the name attribute on the
<Resource>
tag in the context.xml
described above. This is a must.
At this point, your web application can perform the lookup of the Repository object, create and use a Session, and then close the Session. Here's an example of a JSP page that does this:
<%@ page import="
javax.naming.*,
javax.jcr.*,
org.jboss.security.config.IDTrustConfiguration
" %>
<%!
static {
// Initialize IDTrust
String configFile = "security/jaas.conf.xml";
IDTrustConfiguration idtrustConfig = new IDTrustConfiguration();
try {
idtrustConfig.config(configFile);
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
%>
<%
Session sess = null;
try {
InitialContext initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
Repository repo = (Repository) envCtx.lookup("jcr/local");
sess = repo.login(new SimpleCredentials("readwrite", "readwrite".toCharArray()));
// Do something interesting with the Session ...
out.println(sess.getRootNode().getPrimaryNodeType().getName());
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (sess != null) sess.logout();
}
%>
Since this uses a servlet container, there is no JAAS implementation configured, so note the
loading of IDTrust to create the JAAS realm. (To make this work in Tomcat, the security
folder that contains the jaas.conf.xml
, users.properties
, and
roles.properties
needs to be moved into the %CATALINA_HOME%
directory.
Moving the security folder into the conf
directory does not allow those files
to be visible to the JSP page.)
If you use an application server such as JBoss EAP, you could just configure the JAAS realm as part of the server configuration and be done with it.
ModeShape is a Maven-based project. If your application is using Maven, it is very easy to add a dependency on ModeShape's JCR library (plus any extensions), and Maven will ensure your application has access to all of the ModeShape artifacts and all 3rd-party libraries upon which ModeShape depends. Simply add a dependency in your application's POM:
<dependency>
<groupId>org.modeshape</groupId>
<artifactId>modeshape-jcr</artifactId>
<version>1.0.0.Final</version>
</dependency>
plus dependencies for each optional extension (sequencers, connectors, MIME type detectors, etc.):
<dependency>
<groupId>org.modeshape</groupId>
<artifactId>modeshape-connector-store-jpa</artifactId>
<version>1.0.0.Final</version>
</dependency>
...
<dependency>
<groupId>org.modeshape</groupId>
<artifactId>modeshape-sequencer-java</artifactId>
<version>1.0.0.Final</version>
</dependency>
Then, continue by defining a JcrConfiguration
and building the engine, as discussed earlier.
This is very straightforward, and this is exactly what the ModeShape examples do.
The ModeShape community has solicited input on how we can make it easier to consume and use ModeShape in applications that do not use Maven. Check out the discussion thread, and please add any suggestions or opinions!
If you're currently using JBoss DNA, we highly recommend migrating to ModeShape. The process is actually pretty straightforward, since most of your code is probably using the JCR API. (Thank goodness for standards!)
First, change over to use the ModeShape artifacts in Maven. In your application's POM:
change the group ID from "org.jboss.dna
" to "org.modeshape
";
change the artifact IDs to use the "modeshape-
" prefix instead of "dna-
"; and
change the version numbers to "1.0.0.Beta1
".
Your Maven dependencies should then look like what is shown in the Maven chapter.
None of the third-party dependencies have changed from JBoss DNA 0.7 to ModeShape 1.0.0.Final.
Second, change your Java code to use the ModeShape classes.
Wherever your code is using JBoss DNA classes, simply replace the "org.jboss.dna...
" imports
with "org.modeshape...
". All of the method names are the same, and nearly all of the class names are the same.
(The names of some internal classes did begin with "Dna", and these were changed to begin with "ModeShape". But you're not
likely using any of these internal classes anyway.)
Third, in your configuration files, change the JBoss DNA namespace URIs to the ModeShape namespace URIs. In other words,
any namespace URI starting with "http://www.jboss.org/dna
" should be changed to start with
"http://www.modeshape.org
". We also suggest changing the prefixes to "mode
", but
this is technically optional.
What about persisted data? ModeShape will automatically convert any names or paths that use the old DNA namespaces to instead use the corresponding ModeShape namespaces. And, when that data is modified and the node updated, the ModeShape namespaces will be saved in the store.
Be aware that any string property values that contain the DNA namespaces and that are treated by your application as strings will not be changed. The automatic conversion only happens when Name or Path objects are created, or when string values are converted to Name or Path objects.
The search indexes are not automatically converted, so the easiest solution is to simply rebuild the indexes.
One final note. If you've begun using the "Simple" JPA connector model just recently introduced in JBoss DNA 0.7, please be aware that we've changed the names of the database tables. We could have kept using the "DNA_" prefix, but we thought it best to change the name because the "Simple" model is so new and will be with ModeShape for a long time to come.
This chapter outlines how you configure ModeShape, how you then access a javax.jcr.Repository
instance,
and use the standard JCR API to interact with the repository. The
next chapter talks about using the JCR API with your ModeShape repository.