JBoss.orgCommunity Documentation

Chapter 9. Using the JCR API with ModeShape

9.1. Obtaining JCR Repositories
9.2. Creating JCR Sessions
9.2.1. Using JAAS
9.2.2. Using Custom Security
9.2.3. Using HTTP Servlet security
9.2.4. Guest (Anonymous) User Access
9.3. JCR Specification Support
9.3.1. Level 1 and Level 2 (Required) Features
9.3.2. Optional Features
9.3.3. JCR Security
9.3.4. Built-In Node Types
9.3.5. Custom Node Type Registration
9.4. Summary

The Content Repository for Java technology API 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 under JSR-283. ModeShape provides a partial JCR 1.0 implementation that allows you to work with the contents of a repository using the JCR API. For information about how to use the JCR API, please see the JSR-170 specification.

Note

In the interests of brevity, this chapter does not attempt to reproduce the JSR-170 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.

Using ModeShape within your application is actually quite straightforward. As you'll see in this chapter, the first step is setting up ModeShape and starting the JcrEngine. After that, you obtain the javax.jcr.Repository instance for a named repository and just use the standard JCR API throughout your application.

Once you've obtained a reference to a JcrEngine as described in the previous chapter, obtaining a repository is as easy as calling the getRepository(String) method with the name of the repository that you just configured.

String repositoryName = ...;
JcrEngine jcrEngine = ...;
Repository repository = jcrEngine.getRepository(repositoryName);

At this point, your application can proceed by working with the JCR API.

Once you have obtained a reference to the JCR Repository, you can create a JCR session using one of its login(...) methods. The JSR-170 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 = ...;
Session session = repository.login(new SimpleCredentials(userName, password), workspaceName);

If a LoginContext is available for the user, that can be used as part of the credentials to authenticate the user with ModeShape instead. This snippet uses an anonymous class to provide the login context, but any class with a LoginContext getLoginContext() method can be used as well.

final LoginContext loginContext = ...;
Session session = repository.login(new Credentials() {
	LoginContext loginContext getLoginContext() {
		return loginContext;
	}
}, workspaceName);

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. The first step is to provide a custom implementation of SecurityContext that integrates with your application security, allowing ModeShape to discover the authenticated user's name, determine whether the authenticated user has been assigned particular roles (see the JCR Security section), and to notify your application security system that the authenticated session (for JCR) has ended.

The next step is to wrap your SecurityContext instance within an instance of SecurityContextCredentials, and pass it as the Credentials parameter in one of the two login(...) methods:

SecurityContext securityContext = new CustomSecurityContext(...);
Session session = repository.login(new SecurityContextCredentials(securityContext));
			

Once the Session is obtained, the repository content can be accessed and modified like any other JCR repository.

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 = ...;
SecurityContext securityContext = new ServletSecurityContext(request);
Session session = repository.login(new SecurityContextCredentials(securityContext));

You'll note that this is just a specialization of the custom security context approach, since the ServletSecurityContext just implements the SecurityContext interface and delegates to the HttpServletRequest. Feel free to use this class in your servlet-based applications.

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 also supports the optional JCR locking, observation, query, and versioning features. However, ModeShape's behavior with regard to events upon deletion follows the updated behavior outline in the JSR-283 specification (namely that when a subgraph is deleted, ModeShape generates only one event for the top-level node in that subgraph).

ModeShape now supports the optional SQL query feature as defined by JSR-170 and the JCR-SQL query language, though the "/jcr:system" content does not appear in the query results as expected (see MODE-760). ModeShape also supports its replacement, the JCR-SQL2 query language defined by the JSR-283 specification. JCR-SQL2 is much improved and much more capable. For details on both of these query languages, see the chapter on queries and search languages.

Although the JSR-170 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", and "unlock_any"). The register_type and register_namespace permissions restrict 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). 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.


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:


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 prior to the 1.0 release. A CONNECT role is already being used by the ModeShape REST Server to control whether users have access to the repository through that means.

Although the JSR-170 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 or at runtime through a ModeShape-specific interface. ModeShape supports defining node types either through a JSR-283-like template approach or through the use of Compact Node Definition (CND) files. Both type registration mechanisms are supported equally within ModeShape, although the CND approach for defining node types is recommended.

Note

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

The JSR-283 specification provides a useful means of programmatically defining JCR node types. ModeShape supports a comparable node type definition API that implements the functionality from the specification, albeit with interfaces in the org.modeshape.jcr.nodetype package. The intent is to deprecate these classes and replace their usage with the JSR-283 equivalents when ModeShape fully supports the JSR-283 final adopted specification in a future release.

Node types can be defined like so:

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

// Obtain the ModeShape-specific node type manager ...
JcrNodeTypeManager nodeTypeManager = (JcrNodeTypeManager) 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.setRequiredPrimaryTypes(new String[] { "nt:file" });
childNode.setDefaultPrimaryType("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.

Custom node types can be defined more succinctly through the Compact Node Definition file format. 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

This definition could then be registered as part of the repository configuration, using the JcrConfiguration class (see the previous chapter). Or, you can also use a Session to declare the node types in a CDN file, but this also requires ModeShape-specific interfaces and classes:

String pathToCndFileInClassLoader = ...;
CndNodeTypeSource nodeTypeSource = new CndNodeTypeSource(pathToCndFileInClassLoader);

for (Problem problem : nodeTypeSource.getProblems()) {
    System.err.println(problem);
}
if (!nodeTypeSource.isValid()) {
    throw new IllegalStateException("Problems loading node types");
}

Session session = ... ;
// Obtain the ModeShape-specific node type manager ...
Workspace workspace = session.getWorkspace();
JcrNodeTypeManager nodeTypeManager = (JcrNodeTypeManager) workspace.getNodeTypeManager();
nodeTypeManager.registerNodeTypes(nodeTypeSource);

The CndNodeTypeSource class actually implements the JcrNodeTypeSource interface, so other implementations can actually be defined. For more information, see the JavaDoc for JcrNodeTypeSource.

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:

String unusedNodeTypeName = ...;

Session session = ... ;
// Obtain the ModeShape-specific node type manager ...
Workspace workspace = session.getWorkspace();
JcrNodeTypeManager nodeTypeManager = (JcrNodeTypeManager) workspace.getNodeTypeManager();
nodeTypeManager.unregisterNodeType(Collections.singleton(unusedNodeTypeName));

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.