JBoss.orgCommunity Documentation
Using ModeShape within your application is actually quite straightforward, and with JCR 2.0 it is possible for your
application to do everything using only the JCR 2.0 API. Your application will first obtain a javax.jcr.Repository
instance,
and will use that object to create sessions through which your application will read, modify, search,
or monitor content in the repository.
However, before you can use ModeShape, you need to configure it, and that's what this chapter covers.
There really are three options:
Load from a file
is conceptually the most straightforward and requires the least amount
of Java code, but it does requires having a configuration file. This is easy, allows one to manage configurations in version control,
and will likely be the approach most applications use.
Programmatic configuration
allows an application to define and edit
a configuration using Java code. This is useful when you cannot pre-define your configuration, or when you want to start
with a baseline configuration, make programmatic changes based upon some inputs or preferences, and then save
the configuration to a file.
Load from a configuration repository
is an advanced technique
that allows multiple JcrEngine
instances (usually in different processes perhaps on different machines)
to easily access a (shared) 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:predefinedWorkspaceNames>workspace2</mode:predefinedWorkspaceNames>
<mode:predefinedWorkspaceNames>workspace3</mode:predefinedWorkspaceNames>
</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>
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.
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.)
Sometimes your applications can simply define a JcrConfiguration
and instantiate the JcrEngine
instance directly.
This is very straightforward, and this is what the examples in the Getting Started guide 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");
// name in JNDI is defined by configuration
Repository repo = (Repository) envCtx.lookup("jcr/local");
// 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. ModeShape will first treat the value of this
property as a resource name and attempt to load it from the classpath. If no resource can be found with that name,
ModeShape will assume that it is supposed to be a filename (with or without a path) and load it from the filesystem.
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.
Before you deploy ModeShape into your application or its environment, you need to make sure that all of the ModeShape JARs are on the appropriate classpath. Two different scenarios are covered in this section: Maven-based, and using JARs with the traditional classpath.
By far the easiest way to use ModeShape is to use Maven, because with just a few lines of code, Maven will automatically pull all the JARs and source for all of the ModeShape libraries as well as everything those libraries need. All of ModeShape's artifacts for each release are published in the new JBoss Maven repository under the "org.modeshape" group ID.
The JBoss Maven repository not only contains all of the artifacts for ModeShape and other open source projects hosted at JBoss.org, but it also proxies quite a few other repositories that contain many other third-party libraries.
So if you're using Maven (or Ivy), first make sure your project knows about this new JBoss Maven repository. One way to do this is to add the following to your project POM (you'll still likely want to use other Maven repositories for third-party artifacts):
<repositories>
<repository>
<id>jboss</id>
<url>http://repository.jboss.org/nexus/content/groups/public/</url>
</repository>
</repositories>
Or, you can add this information to your ~/.m2/settings.xml
file. For more information,
see the JBoss wiki page.
Then, simply modify your project's POM by adding dependencies on the ModeShape JCR library:
<dependency>
<groupId>org.modeshape</groupId>
<artifactId>modeshape-jcr</artifactId>
<version>2.0.0.Final</version>
</dependency>
This adds only the minimal libraries required to use ModeShape, so you need to add dependencies for each of the connectors and sequencers you want to use. Here is the list of available sequencers:
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-sequencer-cnd</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-sequencer-ddl</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshapce</groupid>
<artifactid>modeshape-sequencer-images</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-sequencer-classfile</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-sequencer-java</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-sequencer-mp3</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-sequencer-msoffice</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-sequencer-xml</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-sequencer-text</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-sequencer-zip</artifactid>
<version>2.0.0.Final</version>
</dependency>
Here is the list of available connectors:
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-connector-filesystem</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-connector-infinispan</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-connector-jcr</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-connector-jbosscache</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-connector-jdbc-metadata</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-connector-store-jpa</artifactid>
<version>2.0.0.Final</version>
</dependency>
<dependency>
<groupid>org.modeshape</groupid>
<artifactid>modeshape-connector-svn</artifactid>
<version>2.0.0.Final</version>
</dependency>
The sequencer and connector libraries you choose, plus every third-party library they need, will be pulled in automatically by Maven into your project.
ModeShape is designed to use the same logging framework as your application, and it uses SLF4J to accomplish this. In other words, ModeShape depends upon the SLF4J API library, but requires you to provide provide a logging implementation as well as the appropriate SLF4J binding JAR.
For example, if your application is using Log4J, your application will already have a dependency for it, and so ModeShape log messages will be sent to the same logging system used in your application, you need to add a dependency to the SLF4J-to-Log4J binding JAR:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.11</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
Of course, SLF4J works with other logging frameworks, too. Some logging implementations (such as LogBack) implement the SLF4J API natively, meaning they require no binding JAR. For details on the options and how to configure them, see the SLF4J manual.
If your application doesn't use Maven, you'll need to obtain the ModeShape JARs and place them onto your application's classpath. ModeShape provides a single download with all of the JARs for all ModeShape components and all dependencies. This file contains the following:
modeshape-jcr-2.0.0.Final-with-dependencies.jar
contains the JARs
necessary to run the core ModeShape JCR repository engine,
the in-memory connector, and the federating connector;
one modeshape-connector-<type>-2.0.0.Final-with-dependencies.jar
for each type of connector, each containing the JARs necessary
for that connector;
one modeshape-sequencer-<type>-2.0.0.Final-with-dependencies.jar
for each type of sequencer, each containing the JARs necessary
for that sequencer;
modeshape-mimetype-detector-aperture-2.0.0.Final-with-dependencies.jar
contains all of the JARs required for the component that detects
the MIME type of files based upon names and/or content; and
modeshape-jpa-ddl-gen-2.0.0.Final-jar-with-dependencies.jar
contains all of the JARs required to run the DDL generation utility.
Note that the core engine is required in all configurations. The jcr-2.0.jar
file is not included and must be provided by you.
And, as mentioned in the previous section, ModeShape uses SLF4J for logging and you must provide
a logging implementation as well as the appropriate SLF4J binding JAR.
This chapter outlines how you configure ModeShape, how to deploy ModeShape into your application, and how to set up your application's environment with the required ModeShape JARs. The next chapter talks about how your application can use the JCR API to access ModeShape repositories.