JBoss Community Archive (Read Only)

ModeShape 2.8

Using the JCR API with ModeShape

The Content Repository for Java Technology API 2.0 provides a standard Java API for working with content repositories. Abbreviated "JCR", this API was developed as part of the Java Community Process under JSR-170 (JCR 1.0) and has been revised and improved as JCR 2.0 under JSR-283. Some of the improvements make it possible for your application to be written entirely against the JCR 2.0 API.

In the interests of brevity, this chapter does not attempt to reproduce the JSR-283 specification nor provide an exhaustive definition of ModeShape JCR capabilities. Rather, this chapter will describe any deviations from the specification as well as any ModeShape-specific public APIs and configuration. So, for a detailed explanation of the JCR API and its many interfaces and methods, see the JSR-283 specification.

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. JCR sessions are designed to be lightweight, so it is perfectly fine (and actually recommended) for your application to create many short-lived sessions while generally avoiding longer-lived sessions. In fact, javax.jcr.Session objects are not required to be thread-safe (and are not in ModeShape), so your application should avoid using a single Session instance in multiple threads.

What's new in JCR 2.0?

Before we get started talking about how to use ModeShape via the standard JCR 2.0 API, it's worth spending a little time talking about the changes in JCR 2.0 compared with JCR 1.0.

Although an application written against the JCR 1.0 API will for the most part work very well against a JCR 2.0 repository, there are a few improvements to the JCR 2.0 API that your application will likely want to leverage.

Let's look at some of the more important changes in the JCR 2.0 API. However, this is certainly not definitive nor a complete comparison, so please consult the JSR-283 specification.

Connecting

JCR 1.0 did not specify a way for client applications to obtain the Repository instance, though the JCR 1.0 specification did state this is typically done through JNDI. Consequently, JCR clients either used the JNDI approach or were required to use implementation-specific code. Often, client applications abstracted this process to minimize their reliance upon implementation-specific interfaces.

While the JNDI approach still works, JCR 2.0 introduces a new mechanism that makes it possible to find a Repository instance using only the JCR API. Details of this are covered more in later, but suffice to say that ModeShape does support this new RepositoryFactory approach.

How this affects your application: If your application used an implementation-specific approach to obtaining a Repository instance, you might consider changing it to use the new RepositoryFactory mechanism.

Identifiers

JCR 1.0 used the notion of UUIDs on referenceable nodes - in other words those nodes with the "mix:referenceable" mixin. However, there were several disadvantages to this design. First, non-referenceable nodes had no such identifier in the JCR API, leading to difficulties in easily identifying nodes using an immutable and invariant identifier (unlike the path, which can change at any time) and requiring a fair amount of code to check whether a node is referenceable before its UUID could safely be obtained. Second and perhaps more importantly, only valid UUIDs could be used to identify nodes. This can cause difficulty when JCR is used as an API to another system that does not use UUIDs.

JCR 2.0 introduces the notion of an identifier on all nodes, and the format of this identifier is designed to be opaque to the client applications. This dramatically reduces the code to access a node's identifier down to a simple method call. And it makes it possible for an implementation to use any identifiers format. This is good for ModeShape federation, as connectors no longer need to force UUIDs for all nodes.

How this affects your application: The Node.getUUID() method is now deprecated, and instead your code should call Node.getIdentifier(), which works on any node. However, be aware that the resulting identifier is no longer required to be a valid UUID. ModeShape does support these methods and behavior.

Binary Values

JCR 1.0 has always supported storing binary values in properties, but clients could do little more than just stream the bytes for each value. JCR 2.0 introduces a Binary interface that defines a way to get the size of the binary value, an InputStream to the value, a method for random access to the value's bytes, and a way to dispose of the binary value when completed (allowing the implementation to better clean up memory and other resources).

How this affects your application: The way your existing JCR application accesses and sets binary values will still work, but the methods are now deprecated. Therefore, you will very likely want to change to use the new Binary interface. For example, code that previously accessed the input stream directly from the Property:

Property property = ...
InputStream stream = property.getInputStream();
try {
   // Read stream
} finally {
   stream.close();
}

can be minimally changed to first get the Binary value and then get the stream from this Binary value:

Property property = ...
InputStream stream = property.getBinary().getInputStream();
try {
   // Read stream
} finally {
   stream.close();
}

This second example is not using any deprecated methods, but does not actually dispose of the Binary object. This actually works just fine in ModeShape, as closing the InputStream will automatically dispose of the Binary object.

You may also consider whether your application may benefit from the new Binary.getSize() or Binary.read(byte[],long) methods.

Node Type Management

In JCR 1.0, client applications could discover node types, property definitions, and child node definitions, but the API did not provide a way for client applications to modify or create new node types. This has been rectified in the JCR 2.0 API, and is these methods are now supported by ModeShape.

Additionally, the JCR 2.0 specification formalized the Compact Node Definition grammar, and made a few minor improvements to the CND formats used in some JCR 1.0 implementations. Earlier ModeShape releases supported the older CND format, and ModeShape 2.8.0.Final now supports the grammar as defined in the specification.

How this affects your application: Your application can now define its node types using the standard CND format and/or using the new programmatic mechanism. If you already used the older CND format, be aware of the few new options available when defining property definitions (e.g., searchable, queryable, etc.). Note that node type discovery is largely unchanged.

Remember to specify the system workspace name for your repositories if dynamically adding or modifying node types. Otherwise, ModeShape will not persist your node type changes.

Queries

JCR 1.0 made it possible for applications to query the repository using XPath and JCR-SQL query languages. JCR 2.0 maintains the (mostly) similar Java interfaces for executing queries, but it deprecates the XPath and JCR-SQL query languages and introduces a new declarative language called "JCR-SQL2" that is a very good improvement over JCR-SQL. JCR 2.0 also introduces a new query object model (called "JCR-QOM") for defining queries using a programmatic API.

ModeShape supports all of these languages (XPath, JCR-SQL, JCR-SQL2, JCR-QOM), and also supports a full-text query language that is defined by the full-text search expression in the JCR-SQL2 language. Additionally, ModeShape extends most of these languages to support richer and more capable queries.

How this affects your application: Your application can continue to use XPath and JCR-SQL queries. However, your application may benefit from switching from JCR-SQL to JCR-SQL2 and its greater capabilities and expressive power. Leverage some of the ModeShape extensions to make your JCR-SQL2 queries even more powerful.

Workspace Management

Applications could not use the JCR 1.0 API to create or destroy workspaces, meaning such operations could only be done through a non-standard and implementation-specific API. The JCR 2.0 API now standardizes these operations, and although not all implementations are required to support them, ModeShape does support these (though not all connectors do support them).

How this affects your application: Your application can now create and remove workspaces using the standard JCR 2.0 API.

Observation

Applications could use the JCR 1.0 API to be notified of changes to the content, using the optional observation feature. However, the JCR 1.0 API required multiple events to be created when a subtree was moved or deleted. This requirement has been relaxed in JCR 2.0 and ModeShape now fully supports the optional observation feature.

How this affects your application: Your application can now use specification-compliant JCR 2.0 observation with ModeShape.

Locking

JCR 1.0 API had the notion of locking nodes, useful in situations that required synchronization around reading and modifying content. This optional API is simple and clean, and worked quite well. The JCR 2.0 API preserved all of the JCR 1.0 locking semantics, but added a few (optional) methods. ModeShape implements this optional locking feature.

How this affects your application: If your application is already using the JCR 1.0 locking feature, be aware that many of the locking-related methods on Node were deprecated in JCR 2.0 and moved to the new LockManager interface. However, locking semantics remain unchanged.

Remember to specify the system workspace name for your repositories if clustering or if the lock information is to be persisted beyond the lifetime of the ModeShape engine.

Versioning

Versioning of nodes was defined as an optional feature of the JCR 1.0 API. The JCR 2.0 API expanded upon locking by defining a simple versioning model, introducing the VersionManager interface, and making some semantic changes as well. For example, restoring a version that contained a versioned child in its subgraph no longer automatically restores the versioned child. This behavior was ambiguous in the JCR 1.0 specification, and ModeShape 1.x performed the restore operation recursively down the graph. The JCR 2.0 specification more clearly requires a non-recursive restore. Therefore, ModeShape 2.8.0.Final now supports the "full versioning" model.

How this affects your application: If your application is already using JCR 1.0 versioning feature, be aware that many of the version-related methods on Node were deprecated in JCR 2.0 and moved to the new VersionManager interface. Also, any reliance upon ModeShape's recursive restore operation must be changed, per the JCR 2.0 specification.

Remember to specify the system workspace name for your repositories if using versioning. Otherwise, ModeShape will not persist your versioning information.

Importing and Exporting

Importing and exporting content is largely unchanged in JCR 2.0, with the exception of specific requirements on handling node identifiers.

How this affects your application: Exporting from a JCR 1.0 or 2.0 repository and importing into a JCR 2.0 repository should work as before. ModeShape does support importing and exporting.

Shareable Nodes

JCR 2.0 introduced the notion of shareable nodes, which allows a node that exists under one parent to be shared under multiple other nodes. These are similar to symbolic links in a *nix file system. For more details about how to create and use shareable nodes, please see the JCR 2.0 specification.

How this affects your application: Your application can now use specification-compliant JCR 2.0 shareable nodes with ModeShape.

Orderable Child Nodes

Orderable child nodes was an optional feature in JCR 1.0, and has been carried over to JCR 2.0 unchanged. Node ordering has been supported by ModeShape since the initial release.

How this affects your application: No changes are required if your application relies upon node ordering.

Paths

As defined in JCR 1.0, paths only consisted of segments with node names. JCR 2.0 adds a new form of path called "identifier paths" that are of the form '[' identifier ']', where identifier is an opaque identifier. (Note that the JCR 2.0 specification might appear to allow identifier segments and name segments to be used together, but Section 3.4.1.1 requires that an identifier segment must be the first and only segment in a path.)

How this affects your application: Any application written to JCR 1.0 paths will likely work as expected (this is certainly true when using ModeShape repositories). However, with JCR 2.0 it is now possible for your application to start making us of identifier paths. For example, PATH properties can now store identifier paths, and it is possible to resolve an identifier path to the actual node. And while the specification does not preclude an implementation returning an identifier path as the node's absolute path, ModeShape never does this and will always return the name-oriented path.

getItem(String)

The JCR 1.0 specification was slightly ambiguous in defining how the getItem(String) method behaved if the relative path could resolve to a node or a property. ModeShape always implemented this by first attempting to resolve to a node, and only if no such node could be found would it attempt to resolve to a property. The JCR 2.0 specification now explicitly specifies this behavior (see Section 3.4.2.2).

How this affects your application: Your application will need to change if it uses getItem(String) and expects relative paths to be resolved against properties before nodes, as this is clearly different from the JCR 2.0 specified behavior. Otherwise, your application needs no changes with respect to getItem(String).

Obtaining a JCR Repository

Before your application can use a JCR repository, it has to find it. As mentioned above, the JCR 2.0 API defines a new RepositoryFactory interface that can be used with the Java Standard Edition Service Loader mechanism to obtain a Repository instance, all using the JCR API alone:

Map<String,String> parameters = ...
Repository repository = null;
for (RepositoryFactory factory : ServiceLoader.load(RepositoryFactory.class)) {
    repository = factory.getRepository(parameters);
    if (repository != null) break;
}

This code looks for all RepositoryFactory implementations on the classpath (assuming those implementations properly defined the service provider within their JARs), and will ask each to create a repository given the supplied parameters. Thus, the parameters are specific to the implementation you want to use.

With JCR 1.0, applications could only find a Repository instance using implementation-specific code. This new JCR 2.0 approach is a bit more complicated, but should work with most JCR 2.0 implementations and does not require using any implementation classes. And your application can even load the parameters from a configuration resource, meaning nothing in your application depends on a particular JCR implementation.

ModeShape uses a single property named "org.modeshape.jcr.URL" with a value that is a URL that either resolves to a ModeShape configuration file. Pointing directly to a configuration file often works well in stand-alone applications or where the configuration is managed in a central system. JNDI works great for applications deployed to server platforms (e.g., an application server or servlet container) where multiple applications might want to use the same JCR repository (or same ModeShape engine). We'll see in the next section how to configure ModeShape's JcrEngine explicitly and register it in JNDI.

So, here's the ServiceLoader example again, but with ModeShape-specific parameters:

String configUrl = ... ; // URL that points to your configuration file
Map<String,String> parameters = Collections.singletonMap("org.modeshape.jcr.URL", configUrl);

Repository repository = null;
for (RepositoryFactory factory : ServiceLoader.load(RepositoryFactory.class)) {
    repository = factory.getRepository(parameters);
    if (repository != null) break;
}

Once you've gotten hold of a Repository instance, you can use it to create Sessions, using code similar to:

Credentials credentials = ...; // JCR credentials
String workspaceName = ...;  // Name of repository workspace
Session session = repository.login(credentials,workspaceName);

We'll talk about the various ways of creating sessions in a later chapter. First, let's look at the various kinds of URLs that you can use.

Configuration File URLs

The value of configUrl in the code snippets can be any URL that is resolvable on your system. For example:

file://path/to/configFile.xml?repositoryName=MyRepository

In this example, the configuration file that specifies the repository setup will be loaded from the file path relativePathToConfigFile and the repository named yourRepositoryName will be returned. If ModeShape cannot find a file at the given path, it will try to load a configuration file as a resource through the classloader.

You might have noticed that this URL contains a query parameter (the "?repositoryName=MyRepository" part). ModeShape strips all query parameters when attempting to resolve file: URLs to the underlying file.

Here's another example of a file URL that uses an absolute path to the file:

file://path/to/configFile.xml?repositoryName=MyRepository

Note the addition of the three forward slashes after the protocol portion of the URL (i.e., file:). These indicate the path is absolute.

Other URLs are possible, too. Here is a URL that points to a configuration file stored in a web-enabled service, such as a web server, WebDAV file share, or version control system:

http://www.example.com/path/to/configFile.xml?repositoryName=MyRepository

Unlike with "file:" URLs, ModeShape does not strip the URL's query parameters when resolving to the configuration file, since most web servers ignore any query parameters not needed. This allows you to include additional query parameters in the URL if they're needed to retrieve the file from the server.

If your platform supports URLs with the "classpath:" scheme, you can point to a resource file on the classpath:

classpath:path/to/configFile.xml?repositoryName=MyRepository

Not all environments have such support, however. Many application servers, including JBoss AS and EAP, do include support by default. However, the Java Standard Edition (SE) does not come with a "classpath:" URL handler, though it is easy to add.

ModeShape does the same thing with all of these URLs: it looks to see whether it already has started a JcrEngine with a configuration file at the given URL. If so, it uses the value of the "repositoryName" query parameter and passes it to the getRepository(String) method. The result of this method call will be a Repository object that is then returned from the factory.

However, if the RepositoryFactory has not yet seen this URL, it will download the configuration file at the URL, load it using a new JcrConfiguration object, and start a new JcrEngine instance. It then uses the "repositoryName" query parameter to obtain the Repository as mentioned above.

Using JNDI URLs

The previous section showed how to use a URL to a configuration file to start a new ModeShape instance. However, ModeShape can be deployed and managed as a central, shared service in a variety of environments, including JBoss AS and EAP. Since a single ModeShape instance can manage multiple repositories, using a single shared instance will have a smaller footprint than multiple ModeShape instances each running a single repository. Plus, the central ModeShape instance can be configured, monitored, administered, and managed without requiring each application to perform these functions.

The easiest and most common way for applications to find and reuse this central, shared ModeShape service is to use JNDI. ModeShape's RepositoryFactory implementation accepts "jndi:" URLs instead of the file-based URL described in the previous chapter. The format of these JNDI URLs is:

jndi:name/in/jndi?repositoryName=MyRepository

The RepositoryFactory will look for a ModeShape engine registered in JNDI at "name/in/jndi", and will ask that engine for the Repository instance with the name "MyRepository". Note that when a JNDI URL is used, RepositoryFactory is will never create its own ModeShape engine instance: if none can be found in JNDI, the RepositoryFactory will simply return null.

Sometimes a JNDI implementation will require creating a new InitialContext instance with a hashtable of environment parameters. If this is the case for your environment, simply include those extra parameters in the Map passed into the getRepository(Map) method. ModeShape will forward these extra parameters into the InitialContext constructor it uses look up the JNDI reference.

Cleaning Up after JcrRepositoryFactory

If your application uses RepositoryFactory with a ModeShape URL pointing to a configuration file, the RepositoryFactory creates an embedded ModeShape engine (or several, if multiple configuration files are used) that maintains a serious of connections, thread pools, and other resources. In these cases, your application should shutdown ModeShape so that it can properly release all accumulated resources.

The JSR-283 specification does not specify a standard way to shutdown engines or repositories created as a side effect of RepositoryFactory, so ModeShape has an extension to the JSR-283 API that provides this capability.

When you obtain your Repository instance using the ServiceLoader mechanism described earlier, keep a reference to the RepositoryFactory that returns a non-null Repository:

Map<String,String> parameters = ...
Repository repository = null;
RepositoryFactory factory = null;
for (RepositoryFactory aFactory : ServiceLoader.load(RepositoryFactory.class)) {
    repository = aFactory.getRepository(parameters);
    if (repository != null) {
        factory = aFactory;
        break;
    }
}

Save this reference where your application's shutdown code can access it, then when your application is terminating, check the type of the factory, cast to the ModeShape extension, and call the "shutdown()" method:

if ( factory instanceof org.modeshape.jcr.api.RepositoryFactory ) {
    ((org.modeshape.jcr.api.RepositoryFactory)factory).shutdown();
}

This call to shutdown(...) instructs each of the JcrEngine instances created by the factory to shutdown gracefully and return immediately (without waiting for any of them to complete the shutdown process). If you'd rather block while the engines perform their shutdown, simply supply a timeout:

if ( factory instanceof org.modeshape.jcr.api.RepositoryFactory ) {
    ((org.modeshape.jcr.api.RepositoryFactory)factory).shutdown(30,TimeUnit.SECONDS);
}

This call will wait up to 30 seconds for each JcrEngine to shut down.

ModeShape's JcrEngine

Although the preferred mechanism to obtain a Repository object is through the RepositoryFactory interface described above, there are times when an application wants or needs to have more control over an actual ModeShape engine, which encapsulates everything necessary to run one or more JCR repositories and managing the underlying repository sources, the pools of connections to the sources, the sequencers, the MIME type detector(s), and the Repository implementations.

If your application uses the RepositoryFactory, then you can proceed to the next section.

The first step to programmatically instantiating a ModeShape JcrEngine is to define a configuration file as described in the previous chapter. Then, load that configuration file and check for problems:

JcrConfiguration config = new JcrConfiguration();
configuration.loadFrom(file);
if ( !configuration.getProblems().isEmpty() ) {
    for ( Problem problem : configuration.getProblems() ) {
        // Report these problems!
    }
}

where the file parameter can actually be a File instance, a URL to the file, an InputStream containing the contents of the file, or a String containing the path to the configuration 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 it is very useful if you ModeShape configuration file is embedded within another XML file.)

If your application is coding against the ModeShape classes, you may also consider programmatically creating the configuration. This is useful when you cannot predefine a configuration, but instead have to build one based upon some parameters known only at runtime. Of course, you can always create the configuration programmatically, write that configuration out to a file, and then load the configuration using the standard RepositoryFactory mechanism.

Once you have a valid JcrConfiguration instance with no errors, you can build and start the JcrEngine:

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.

Creating JCR Sessions

Once you have obtained a reference to the JCR Repository, you can create a JCR session using one of its login(...) methods. The JSR-283 specification provides four login methods, but the behavior of these methods depends on the kind of authentication system your application is using.

Using JAAS

The login() method allows the implementation to choose its own security context to create a session in the default workspace for the repository. The ModeShape JCR implementation uses the security context from the current JAAS AccessControlContext. This implies that this method will throw a LoginException if it is not executed as a PrivilegedAction (AND the JcrRepository.Options.ANONYMOUS_USER_ROLES option does not allow access; see below for an example of how to configure guest user access). Here is one example of how this might work:

Subject subject = ...;
{{Session}} session = Subject.doAsPrivileged(subject,
    new PrivilegedExceptionAction<{{Session}}>() {
        public Session run() throws Exception {
            return repository.login();
        }
    }, AccessController.getContext());

Another variant of this is to use the AccessControlContext directly, which then operates against the current Subject:

{{Session}} session = AccessController.doPrivileged(
    new PrivilegedExceptionAction<{{Session}}>() {
        public Session run() throws Exception {
            return repository.login();
        }
    });

Either of these approaches will yield a session with the same user name and roles as subject. The login(String workspaceName) method is comparable and allows the workspace to be specified by name:

Subject subject = ...;
final {{String}} workspaceName = ...;
{{Session}} session = ({{Session}}) Subject.doAsPrivileged(subject,
    new PrivilegedExceptionAction<{{Session}}>() {
        public Session run() throws Exception {
            return repository.login(workspaceName);
        }
    }, AccessController.getContext());

The JCR API also allows supplying a JCR Credentials object directly as part of the login process, although ModeShape imposes some requirements on what types of Credentials may be supplied. The simplest way is to provide a JCR SimpleCredentials object. These credentials will be validated against the JAAS realm named "modeshape-jcr", unless another realm name is provided as an option during the JCR repository configuration. For example:

{{String}} userName = ...;
char[] password = ...;
{{Session}} session = repository.login(new {{SimpleCredentials}}(userName, password));

Similarly, the login(Credentials credentials, String workspaceName) method enables passing the credentials and a workspace name:

{{String}} userName = ...;
char[] password = ...;
{{String}} workspaceName = ...;
{{Credentials}} credentials = new {{SimpleCredentials}}(userName, password);
{{Session}} session = repository.login(credentials, workspaceName);

If you'd want to use a different JAAS realm that what ModeShape is configured to use, you can use a JaasCredentials instance to pass the actual JAAS LoginContext that should be used for authentication and authorization:

{{LoginContext}} loginContext = ...;
{{Credentials}} credentials = new {{JaasCredentials}}(loginContext);
{{String}} workspaceName = ...;
{{Session}} session = repository.login(credentials,workspaceName);

Note that even in this case, ModeShape will still use the same roles for authorization.

Using HTTP Servlet security

Servlet-based applications can make use of the servlet's existing authentication mechanism from HttpServletRequest. Please note that the example below assumes that the servlet has a security constraint that prevents unauthenticated access.

{{HttpServletRequest}} request = ...;
{{Session}} session = repository.login(new {{ServletCredentials}}(request));

The ServletCredentials is just a JCR Credentials implementation that is used by ModeShape's ServletProvider to delegate the authorization requests to HttpServletRequest's "hasRole" method. The ServletCredentials class is in the small "modeshape-jcr-api" module, so feel free to use this class in your servlet-based applications.

Guest (Anonymous) User Access

By default, ModeShape allows guest users full administrative access. This is done to make it easier to get started with ModeShape. Of course, this is clearly not an appropriate security model for a production system.

To modify the roles granted to guest users, change the JcrRepository.Options.ANONYMOUS_USER_ROLES option for your repository to have a different value, like "" (to disable guest access entirely) or "readonly" (to give guests read-only access to all repositories). The value of this option can be any pattern that matches those described in the table below.

The Using ModeShape chapter of the Getting Started Guide provides examples of modifying this option through programmatic configuration or in an XML configuration file.

Once ModeShape is configured properly, getting anonymous JCR sessions requires no authentication. The easiest way to do this is to use the JCR API methods that do not have Credentials parameters. For example, this gets an anonymous session to the default workspace:

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

while the following gets an anonymous session to the workspace with the supplied name:

{{String}} workspaceName = ...;
{{Session}} session = repository.login(workspaceName);

Per the JCR API, these are equivalent to passing a null Credentials reference to "login" methods, so you can choose that approach as well. ModeShape provides the AnonymousCredentials implementation that can be used if your application expects a to use non-null Credentials object:

{{Session}} session = repository.login(new {{AnonymousCredentials}}());

or

{{String}} workspaceName = ...;
{{Session}} session = repository.login(new {{AnonymousCredentials}}(),workspaceName);

If you supply any other Credentials implementation to the "login" methods, ModeShape will not treat it as an anonymous login and will authenticate using JAAS or, if the credentials is a SecurityContextCredentials instance, its SecurityContext instance. In other words, there's no way to turn off authentication, but you can use anonymous sessions.

Using Custom Security

Not all applications can or want to use JAAS for their authentication system, so ModeShape provides a way to integrate your own custom security provider. Most of the steps are outlined in the previous chapter, but when logging in your application needs to use a compatible Credentials implementation, similar to the examples shown above.

JCR Specification Support

We believe that ModeShape JCR implementation is JCR-compliant, but we are awaiting final certification of compliance. Additionally, the JCR specification allows some latitude to implementors for some implementation details. The sections below clarify ModeShape's current and planned behavior. As always, please consult the current list of known issues and bugs.

Required features

ModeShape 2.8.0.Final implements all of the JCR 2.0 required features:

  • repository acquisition

  • authentication

  • reading/navigating

  • query

  • export

  • node type discovery

  • permissions and capability checking

ModeShape supports several query languages, including the JCR-SQL2 and JCR-QOM query languages defined in JSR-283, and the XPath and JCR-SQL languages defined in JSR-170 but deprecated in JSR-283. ModeShape also supports a fulltext search language that is defined by the full-text search expression grammar used in the second parameter of the CONTAINS(...) function of the JCR-SQL2 language. We just pulled it out and made it available as a first-class query language.

Optional features

ModeShape 2.8.0.Final implements most of the JCR 2.0 optional features:

  • writing

  • import

  • observation

  • workspace management

  • versioning

  • locking

  • node type management

  • same-name siblings

  • orderable child nodes

  • shareable nodes
    The remaining optional features (access control management, lifecycle management, retention and hold, and transactions) may be introduced in future versions.

TCK Compatibility features

The ModeShape project has not yet been certified to be fully-compliant with the JCR 2.0 specification, but does plan on attaining this certification in the very near future.

However, the ModeShape project also runs the JCR TCK unit tests from the reference implementation every night. These tests technically do not represent the official TCK, but are used within the TCK. Most of these unit tests are run in the modeshape-jcr module against the in-memory repository to ensure our JCR implementation behaves correctly, and the same tests are run in the modeshape-integration-tests module against a variety of connectors to ensure they're implemented correctly. The modeshape-jcr-tck module runs all of these TCK unit tests, and currently there are only a handful of failures due to known issues (see the JCR specification support section for details).

ModeShape 2.8.0.Final currently passes 1372 of the 1391 JCR TCK tests, where 17 of these 19 failures appear to be bugs in the TCK tests (see JCR-2648, JCR-2661, JCR-2662, and JCR-2663). The remaining 2 failures are due to a known issue (see MODE-760).

JCR Security

Although the JSR-283 specification requires implementation of the Session.checkPermission(String, String) method, it allows implementors to choose the granularity of their access controls. ModeShape supports coarse-grained, role-based access control at the repository and workspace level.

ModeShape has extended the set of JCR-defined actions ("add_node", "set_property", "remove", and "read") with additional actions ("register_type", "register_namespace", "unlock_any", "create_workspace" and "delete_workspace"). The "register_type" and "register_namespace" permissions control the ability to register (and unregister) node types and namespaces, respectively. The "unlock_any"" permission grants the user the ability to unlock any locked node or branch (as opposed to users without that permission who can only unlock nodes or branches that they have locked themselves or for which they hold the lock token). Finally, the "create_workspace" and "delete_workspace" permissions grant the user the ability to create workspaces and delete workspaces, respectively, using the corresponding methods on Workspace. Permissions to perform these actions are aggregated in roles that can be assigned to users.

ModeShape currently defines three roles: readonly, readwrite, and admin. If the Credentials passed into Repository.login(...) (or the Subject from the AccessControlContext, if one of the no-credential login methods were used) have any of these roles, the session will have the corresponding access to all workspaces within the repository. The mapping from the roles to the actions that they allow is provided below, for any values of path.

Action Name

readonly

readwrite

admin

read

Allows

Allows

Allows

add_node

 

Allows

Allows

set_property

 

Allows

Allows

remove

 

Allows

Allows

register_namespace

 

 

Allows

register_type

 

 

Allows

unlock_any

 

 

Allows

create_workspace

 

 

Allows

delete_workspace

 

 

Allows

Role / Action Mapping

In this release, ModeShape does not check that the actions parameter passed into Session.checkPermission(...) contains only valid actions. This check may be added in a future release.

It is also possible to grant access only to one or more repositories on a single ModeShape server or to one or more named workspaces within a repository. The format for role names is defined below:

Role Pattern

Examples

Description

ROLE_NAME

readonly, admin

Grants the named role to the assigned user on every workspace in any repository on the ModeShape server.

ROLE_NAME.REPOSITORY_SOURCE_NAME

readonly.modeshape_source1, admin.modeshape_source2Repository

Grants the named role to the assigned user on every workspace in the named repository on the ModeShape server.

ROLE_NAME.REPOSITORY_SOURCE_NAME.WORKSPACE_NAME

readonly.modeshape_source1.jsmith, admin.modeshape_source2.default

Grants the named role to the assigned user on the named workspace in the named repository on the ModeShape server.

Role Formats

It is also possible to grant more than one role to the same user. For example, the user "jsmith" could be granted the roles "readonly.production", "readwrite.production.jsmith", and "readwrite.staging" to allow read-only access to any workspace in a repository that uses the "production" source, read/write access to a personal workspace on the same production repository, and read/write access to any workspace in a repository using the repository source named "staging".

As a final note, the ModeShape JCR implementation may have additional security roles added in the future. A CONNECT role is already being used by the ModeShape REST Server to control whether users have access to the repository through that means.

Built-In Node Types

ModeShape supports all of the built-in node types described in the JSR-283 specification. ModeShape also defines some custom node types in the mode namespace, but none of these node types (other than mode:resource) are intended to be used by developers integrating with ModeShape and may be changed or removed at any time.

Custom Node Type Registration

Although the JSR-283 specification does not require support for registration and unregistration of custom types, ModeShape supports this extremely useful feature. Custom node types can be added at startup, as noted above, at runtime using the standard JCR API for managing node types, or at runtime by reading CND files or Jackrabbit XML files. These node type registration mechanisms are supported equally within ModeShape, although defining node types in standard CND files is recommended for portability.

ModeShape also supports defining custom node types to load at startup. This is discussed in more detail in the previous chapter.

Managing Node Types

Using the JCR APIThe JCR 2.0 API provides a mechanism for registering and unregistering node types. Registration is done by creating NodeTypeTemplate objects, NodeDefinitionTemplate objects (for child node definitions), and PropertyDefinitionTemplate objects (for property definitions). Use the setter methods to set the various attributes, and then register the node type definition with the NodeTypeManager:

javax.jcr.Session session = ... ;
javax.jcr.Workspace workspace = session.getWorkspace();

// Obtain the node type manager ...
javax.jcr.nodetype.NodeTypeManager nodeTypeManager = workspace.getNodeTypeManager();

// Declare a mixin node type named "searchable" (with no namespace)
NodeTypeTemplate nodeType = nodeTypeManager.createNodeTypeTemplate();
nodeType.setName("searchable");
nodeType.setMixin(true);

// Add a mandatory child named "source" with a required primary type of "nt:file"
NodeDefinitionTemplate childNode = nodeTypeManager.createNodeDefinitionTemplate();
childNode.setName("source");
childNode.setMandatory(true);
childNode.setRequiredPrimaryTypesNames(new String[] { "nt:file" });
childNode.setDefaultPrimaryTypeName("nt:file");
nodeType.getNodeDefinitionTemplates().add(childNode);

// Add a multi-valued STRING property named "keywords"
PropertyDefinitionTemplate property = nodeTypeManager.createPropertyDefinitionTemplate();
property.setName("keywords");
property.setMultiple(true);
property.setRequiredType(PropertyType.STRING);
nodeType.getPropertyDefinitionTemplates().add(property);

// Register the custom node type
nodeTypeManager.registerNodeType(nodeType,false);

Residual properties and child node definitions can also be defined simply by not calling setName on the template.

ModeShape also supports a simple means of unregistering types, although it is not possible to unregister types that are currently being used by nodes or as required primary types or supertypes of other types. Unused node types can be unregistered with the following code, using the standard JCR 2.0 API:

String[] unusedNodeTypeNames = ...;

javax.jcr.Session session = ... ;
javax.jcr.nodetype.NodeTypeManager nodeTypeManager = session.getWorkspace().getNodeTypeManager();
nodeTypeManager.unregisterNodeTypes(unusedNodeTypeNames);

This approach is often used to register custom node types within an application, when the application knows the node type definitions or retrieves these definitions from some persisted format (e.g., file, database, etc.). However, ModeShape provides some utilities if you want to programmatically register node types defined in certain file formats. We'll see in the next section how to use these.

Reading node type definition files

Custom node types can be defined more succinctly through the CND file format defined by the JCR 2.0 specification. In fact, this is how JBoss ModeShape defines its built-in node types. An example CND file that declares the same node type as above would be:

[searchable] mixin
- keywords (string) multiple
+ source (nt:file) = nt:file mandatory

This definition could then be registered as part of the repository configuration (see the previous chapter). Or, you can also use a Session to programmatically register the node types in a CND file, but this requires casting the node type manager to a ModeShape-specific interface in the modeshape-jcr-api module:
javax.jcr.Session session = ...
org.modeshape.jcr.api.nodetype.NodeTypeManager nodeTypeMgr = (org.modeshape.jcr.nodetype.NodeTypeManager)
                              session.getWorkspace().getNodeTypeManager();
boolean allowUpdate = true;
File file = ...  // or InputStream or URL
nodeTypeMgr.registerNodeTypeDefinitions(file,allowUpdate);

ModeShape's NodeTypeManager interface extends the standard javax.jcr.nodetype.NodeTypeManager interface and provides registerNodeTypeDefinitions(...) methods overloaded to accept File, URL, and InputStream instances.

The content of these files can be the standard CND file format or the non-standard XML format used by Jackrabbit.

These methods are also functionally similar to the standard methods that register arrays of NodeTypeDefinition objects in that they have a second allowUpdates parameter specifying whether existing node types can be updated. However, note that these methods will automatically register any namespaces declared in the files but not yet registered with the workspace.

This technique was added to ModeShape 2.8.0.Final, and replaces the now-deprecated CndNodeTypeReader and JackrabbitXmlNodeTypeReader classes, which will be removed in ModeShape 3.0.

Summary

In this chapter, we covered how to use JCR with ModeShape and learned about how it implements the JCR specification. Now that you know how ModeShape repositories work and how to use JCR to work with ModeShape repositories, we'll move on in the next chapter to show how you can use ModeShape to query and search your JCR data.

JBoss.org Content Archive (Read Only), exported from JBoss Community Documentation Editor at 2020-03-11 12:04:39 UTC, last content change 2012-02-24 17:12:54 UTC.