JBoss.orgCommunity Documentation
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.
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.
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.
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.
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.
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.6.0.Beta1 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.
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.
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.
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.
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 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.6.0.Beta1 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 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.
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 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.
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.
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)
.
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.
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.
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.
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.
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.
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.
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 = newJaasCredentials
(loginContext); String workspaceName = ...; Session session = repository.login(credentials,workspaceName);
Note that even in this case, ModeShape will still use the same roles for authorization.
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(newServletCredentials
(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.
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.
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.
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.
ModeShape 2.6.0.Beta1 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.
ModeShape 2.6.0.Beta1 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.
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.6.0.Beta1 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).
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
.
Table 7.1. Role / Action Mapping
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 |
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:
Table 7.2. Role Formats
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_NAME | readonly.modeshape_repo , admin.localRepository | Grants the named role to the assigned user on every workspace in the named repository on the ModeShape server. |
ROLE_NAME.REPOSITORY_NAME.WORKSPACE_NAME | readonly.modeshape_repo.jsmith , admin.localRepository.default | Grants the named role to the assigned user on the named workspace in the named repository on the ModeShape server. |
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 on a production repository,
read/write access to a personal workspace on the same production repository,
and read/write access to any workspace in a staging repository.
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.
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.
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.
The 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:
Session session = ... ; Workspace workspace = session.getWorkspace(); // Obtain the ModeShape-specific node type manager ... 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 = ...; Session session = ... ; 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.
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 ModeShape-specific class to read this file:
Session session = ...CndNodeTypeReader
reader = newCndNodeTypeReader
(session); reader.read(cndFile); // from file, file system path, classpath resource, URL, etc. if (!reader.getProblems().isEmpty()) { for (Problem problem : nodeTypeSource.getProblems()) { // report or record problem } } else { boolean allowUpdate = ... NodeTypeManager nodeTypeManager = session.getWorkspace().getNodeTypeManager(); nodeTypeManager.registerNodeTypes(reader.getNodeTypeDefinitions(), allowUpdate); }
The CndNodeTypeReader
class provides a number of read(...)
methods that accept File
s, paths to files on the file system,
the names of resources on the classpath, , and InputStreams. And CndNodeTypeReader
will also register any namespace mappings
defined in the CND file but not yet registered in the session or workspace. For details, see the JavaDoc for CndNodeTypeReader
.
If you have multiple CND files, you can either call read(...)
multiple times before
registering (as long as the CND files don't contain duplicate node type definitions), or you can simply create and use a new reader
for each CND file. The choice is yours.
ModeShape also provides a class that reads the node types defined in a Jackrabbit XML format. This is useful if you've been using Jackrabbit,
have defined your custom node types in the Jackrabbit-specific format, but want to switch to ModeShape and don't want to have to manually
convert your node types in the standard CND format. This class is used almost identically to the CndNodeTypeReader
class described above:
Session session = ...JackrabbitXmlNodeTypeReader
reader = newJackrabbitXmlNodeTypeReader
(session); reader.read(cndFile); // from file, file system path, classpath resource, URL, etc. if (!reader.getProblems().isEmpty()) { for (Problem problem : nodeTypeSource.getProblems()) { // report or record problem } } else { boolean allowUpdate = ... NodeTypeManager nodeTypeManager = session.getWorkspace().getNodeTypeManager(); nodeTypeManager.registerNodeTypes(reader.getNodeTypeDefinitions(), allowUpdate); }
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.