JBoss.orgCommunity Documentation

Chapter 10. The JBoss DNA RESTful Web Service

10.1. Supported Resources and Methods
10.2. Configuring the DNA REST Server
10.3. Deploying the DNA REST Server
10.4. Repository Providers

JBoss DNA now provides a RESTful interface to its JCR implementation that allows HTTP-based access and updating of content. Although the initial version of this REST server only supports the JBoss DNA JCR implementation, it has been designed to make integration with other JCR implementors easy. This chapter describes how to configure and deploy the REST server.

The REST Server currently supports the URIs and HTTP methods described below. The URI patterns assume that the REST server is deployed at its conventional location of "/resources". These URI patterns would change if the REST server were deployed under a different web context and URI patterns below would change accordingly. Currently, only JSON-encoded responses are provided.


Note that this approach supports dynamic discovery of the available repositories on the server. A typical conversation might start with a request to the server to check the available repositories.

GET http://www.example.com/resources

This request would generate a response that mapped the names of the available repositories to metadata information about the repositories like so:

{
	"dna%3arepository" : { 
		"repository" : {
			"name" : "dna%3arepository",
			"resources" : { "workspaces":"/resources/dna%3arepository" }
		}
	}
}

The actual response wouldn't be pretty-printed like the example, but the format would be the same. The name of the repository ("dna:repository" URL-encoded) is mapped to a repository object that contains a name (the redundant "dna:repository") and a list of available resources within the repository and their respective URIs. Note that JBoss DNA supports deploying multiple JCR repositories side-by-side on the same server, so this response could easily contain multiple repositories in a real deployment.

The only thing that you can do with a repository through the REST interface at this time is to get a list of its workspaces. A request to do so can be built up from the previous response like this:

GET http://www.example.com/resources/dna%3arepository

This request (and all of the following requests) actually create a JCR Session to service the request and require that security be configured. This process is described in more detail in a later section. Assuming that security has been properly configured, the response would look something like this:

{
	"default" : {
		"workspace" : {
			"name" : "default",
			"resources" : { "items":"/resources/dna%3arepository/default/items" }
		}
	}
}

Like the first response, this response consists of a list of workspace names mapped to metadata about the workspaces. The example above only lists one workspace for simplicity, but there could be many different workspaces returned in a real deployment. Note that the "items" resource builds the full URI to the root of the items hierarchy, including the encoding of the repository name and the workspace name.

Now a request can be built to retrieve the root item of the repository.

GET http://www.example.com/resources/dna%3arepository/default/items

Any other item in the repository could be accessed by appending its path to the URI above. In a default repository with no content, this would return the following response:

{
	"properties": {
		"jcr:primaryType": "dna:root",
		"jcr:uuid": "97d7e2ef-996e-4d99-8ec2-dc623e6c2239"
	},
	"children": ["jcr:system"]

The response contains a mapping of property names to their values and an array of child names. Had one of the properties been multi-valued, the values for that property would have been provided as an array as well, as will shortly be shown.

The items resource also contains an option query parameter: dna:depth. This parameter, which defaults to 1, controls how deep the hierarchy of returned nodes should be. Had the request had the parameter:

GET http://www.example.com/resources/dna%3arepository/default/items?dna:depth=2

Then the response would have contained details for the children of the root node as well.

{
	"properties": {
		"jcr:primaryType": "dna:root",
		"jcr:uuid": "163bc5e5-3b57-4e63-b2ae-ededf43d3445"
	},
	"children": {
		"jcr:system": {
			"properties": {"jcr:primaryType": "dna:system"},
    		"children": ["dna:namespaces"]
		}
	}
}

It is also possible to use the RESTful API to add, modify and remove repository content. Removes are simple - a DELETE request with no body returns a response with no body.

DELETE http://www.example.com/resources/dna%3arepository/default/items/path/to/deletedNode

Adding content simply requires a POST to the name of the relative root node of the content that you wish to add and a request body in the same format as the response from a GET. Adding multiple nodes at once is supported, as shown below.

POST http://www.example.com/resources/dna%3arepository/default/items/newNode

{
	"properties": {
		"jcr:primaryType": "nt:unstructured",
		"jcr:mixinTypes": "mix:referenceable",
		"someProperty": "foo"
	},
	"children": {
		"newChildNode": {
			"properties": {"jcr:primaryType": "nt:unstructured"}
		}
	}
}

Note that protected properties like jcr:uuid are not provided but that the primary type and mixin types are provided as properties. The REST server will translate these into the appropriate calls behind the scenes. The response from the request will be empty by convention.

The PUT method allows for updates of nodes and properties. If the URI points to a property, the body of the request should be the new JSON-encoded value for the property.

PUT http://www.example.com/resources/dna%3arepository/default/items/newNode/someProperty

"bar"

Setting multiple properties at once can be performed by providing a URI to a node instead of a property. The body of the request should then be a JSON object that maps property names to their new values.

PUT http://www.example.com/resources/dna%3arepository/default/items/newNode

{
	"someProperty": "foobar",
	"someOtherProperty": "newValue"
}

Note

The PUT method doesn't currently support adding or removing mixin types. This will be corrected in the future. A JIRA issue has been created to help track this issue.

The DNA REST server is deployed as a WAR and configured mostly through its web configuration file (web.xml). Here is an example web configuration that is used for integration testing of the DNA REST server along with an explanation of its parts.

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
                         "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <display-name>JBoss DNA JCR RESTful Interface</display-name>

This first section is largely boilerplate and should look familiar to anyone who has deployed a servlet-based application before. The display-name can be customized, of course.

The next stanza configures the repository provider.

  <!--
    This parameter provides the fully-qualified name of a class that implements
    the o.j.d.web.jcr.rest.spi.RepositoryProvider interface.  It is required
    by the DnaJcrDeployer that controls the lifecycle for the DNA REST server.
  -->
  <context-param>
    <param-name>org.jboss.dna.web.jcr.rest.REPOSITORY_PROVIDER</param-name>
    <param-value>org.jboss.dna.web.jcr.rest.spi.DnaJcrRepositoryProvider</param-value>
  </context-param>

As noted above, this parameter informs the DnaJcrDeployer of the specific repository provider in use. Unless you are using the JBoss DNA REST server to connect to a different JCR implementation, this should never change.

Next we configure the DNA JcrEngine itself.

  <!--
    This parameter, specific to the DnaJcrRepositoryProvider implementation, specifies
    the name of the configuration file to initialize the repository or repositories.
    This configuration file must be on the classpath and is given as a classpath-relative
    directory.
  -->
  <context-param>
    <param-name>org.jboss.dna.web.jcr.rest.CONFIG_FILE</param-name>
    <param-value>/configRepository.xml</param-value>
  </context-param>

If you are not familiar with the file format for a JcrEngine configuration file, you can build one programatically with the JcrConfiguration class and call save(...) instead of build() to output the configuration file that equates to the configuration.

This is followed by a bit of RESTEasy and JAX-RS boilerplate.

  <!--
    This parameter defines the JAX-RS application class, which is really just a metadata class
    that lets the JAX-RS engine (RESTEasy in this case) know which classes implement pieces
    of the JAX-RS specification like exception handling and resource serving.
		
    This should not be modified. 
  -->
  <context-param>
    <param-name>javax.ws.rs.Application</param-name>
    <param-value>org.jboss.dna.web.jcr.rest.JcrApplication</param-value>
  </context-param>

  <!-- Required parameter for RESTEasy - should not be modified -->
  <listener>
    <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
  </listener>

  <!-- Required parameter for JBoss DNA REST - should not be modified -->
  <listener>
    <listener-class>org.jboss.dna.web.jcr.rest.DnaJcrDeployer</listener-class>
  </listener>

  <!-- Required parameter for RESTEasy - should not be modified -->
  <servlet>
    <servlet-name>Resteasy</servlet-name>
    <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
  </servlet>

  <!-- Required parameter for JBoss DNA REST - should not be modified -->
  <servlet-mapping>
    <servlet-name>Resteasy</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

In general, this part of the web configuration file should not be modified.

Finally, security must be configured for the REST server.

  <!-- 
    The JBoss DNA REST implementation leverages the HTTP credentials to for authentication and authorization
    within the JCR repository.  It makes no sense to try to log into the JCR repository without credentials,
    so this constraint helps lock down the repository.
		
    This should generally not be modified. 
  -->
  <security-constraint>
    <display-name>DNA REST</display-name>
    <web-resource-collection>
      <web-resource-name>RestEasy</web-resource-name>
      <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
			<!--  
        A user must be assigned this role to connect to any JCR repository, in addition to needing the READONLY
        or READWRITE roles to actually read or modify the data.  This is not used internally, so another
        role could be substituted here.
      -->
      <role-name>connect</role-name>
    </auth-constraint>
  </security-constraint>

  <!--  
    Any auth-method will work for JBoss DNA.  BASIC is used this example for simplicity.
  -->
  <login-config>
    <auth-method>BASIC</auth-method>
  </login-config>

  <!-- 
    This must match the role-name in the auth-constraint above. 
  -->
  <security-role>
    <role-name>connect</role-name>
  </security-role>
</web-app>

As noted above, the REST server will not function properly unless security is configured. All authorization methods supported by the Servlet specification are supported by JBoss DNA and can be used interchangeable, as long as authenticated users have the connect role listed above.

Deploying the DNA REST server only requires three steps: preparing the web configuration, configuring the users and their roles in your web container (outside the scope of this document), and assembling the WAR. This section describes the requirements for assembling the WAR.

If you are using Maven to build your projects, the WAR can be built from a POM. Here is a portion of the POM used to build the JBoss DNA REST Server integration subproject.

	
<project xmlns="http://maven.apache.org/POM/4.0.0" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<artifactId>dna</artifactId>
		<groupId>org.jboss.dna</groupId>
		<version>0.5-SNAPSHOT</version>
		<relativePath>../..</relativePath>
	</parent>
	<artifactId>dna-web-jcr-rest-war</artifactId>
	<packaging>war</packaging>
	<name>JBoss DNA JCR REST Servlet</name>
	<description>JBoss DNA servlet that provides RESTful access to JCR items</description>
	<url>http://labs.jboss.org/dna</url>
	<dependencies>
		<dependency>
			<groupId>org.jboss.dna</groupId>
			<artifactId>dna-web-jcr-rest</artifactId>
			<version>${pom.version}</version>
		</dependency>

		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.4.3</version>
			<scope>runtime</scope>
		</dependency>
		
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-client</artifactId>
			<version>1.0-beta-8</version>
		</dependency>		
	</dependencies>
</project>

If you use this approach, make sure that web configuration file is in the /src/main/webapp/WEB-INF directory.

The JBoss REST Server WAR is still easy enough to build if you are not using Maven. Simply construct a WAR with the following contents:

+ /WEB-INF
	+ /classes
	|	+ configRepository.xml
	|	+ log4j.properties (Optional)
	+ /lib
	|	+ activation-1.1.jar
	|	+ antlr-runtime-3.1.3.jar
	|	+ commons-codec-1.2.jar
	|	+ commons-httpclient-3.1.jar
	|	+ commons-logging-1.0.4.jar
	|	+ dna-cnd-0.5-SNAPSHOT.jar
	|	+ dna-common-0.5-SNAPSHOT.jar
	|	+ dna-connector-federation-0.5-SNAPSHOT.jar
	|	+ dna-graph-0.5-SNAPSHOT.jar
	|	+ dna-jcr-0.5-SNAPSHOT.jar
	|	+ dna-repository-0.5-SNAPSHOT.jar
	|	+ dna-web-jcr-rest-0.5-SNAPSHOT.jar
	|	+ FastInfoset-1.2.2.jar
	|	+ google-collect-snapshot-20080530.jar
	|	+ hamcrest-core-1.1.jar
	|	+ javassist-3.6.0.GA.jar
	|	+ jaxb-api-2.1.jar
	|	+ jaxb-impl-2.1.9.jar
	|	+ jaxrs-api-1.1-RC2.jar
	|	+ jcip-annotations-1.0.jar
	|	+ jcr-1.0.1.jar
	|	+ jettison-1.0.1.jar
	|	+ joda-time-1.4.jar
	|	+ jsr250-api-1.0.jar
	|	+ junit-dep-4.4.jar
	|	+ resteasy-client-1.0-beta-8.jar
	|	+ resteasy-common-1.0-beta-8.jar
	|	+ resteasy-jaxb-provider-1.1-RC2.jar
	|	+ resteasy-jaxrs-1.1-RC2.jar
	|	+ scannotation-1.0.2.jar
	|	+ servlet-api-2.5.jar
	|	+ sjsxp-1.0.1.jar
	|	+ slf4j-api-1.4.3.jar
	|	+ slf4j-log4j12-1.4.3.jar
	|	+ slf4j-simple-1.5.2.jar
	|	+ stax-api-1.0-2.jar
	|	+ webserver-1.3.3.jar
	+ web.xml
			

If you are using sequencers or any connectors other than the in-memory or federated connector, you will also have to add the JARs for those dependencies into the WEB-INF/lib directory as well. You will also have to change the version numbers on the JARs to reflect the current version of JBoss DNA.

This WAR can be deployed into your servlet container.

The JBoss DNA REST server can also be used as an interface to to other JCR repositories by creating an implementation of the RepositoryProvider interface that connects to the other repository.

The RepositoryProvider only has a few methods that must be implemented. When the DnaJcrDeployer starts up, it will dynamically load the RepositoryProvider implementation (as noted above) and call the startup(ServletContext) method on the provider. The provider can use this method to load any required configuration parameters from the web configuration (web.xml) and initialize the repository.

As an example, here's the DNA JCR provider implementation of this method with exception handling omitted for brevity.

public void startup( ServletContext context ) {
    String configFile = context.getInitParameter(CONFIG_FILE);

     InputStream configFileInputStream = getClass().getResourceAsStream(configFile);
     jcrEngine = new JcrConfiguration().loadFrom(configFileInputStream).build();
     jcrEngine.start();
}

As you can see, the name of configuration file for the JcrEngine is read from the servlet context and used to initialize the engine. Once the repository has been started, it is now ready to accept the main methods that provide the interface to the repository.

The first method returns the set of repository names supported by this REST server.

public Set<String> getJcrRepositoryNames() {
    return new HashSet<String>(jcrEngine.getRepositoryNames());
}

The JBoss DNA JCR repository does support multiple repositories on the same server. Other JCR implementations that don't support multiple repositories are free to return a singleton set containing any string from this method.

The other required method returns an open JCR Session for the user from the current request in a given repository and workspace. The provider can use the HttpServletRequest to get the authentication credentials for the HTTP user.

public Session getSession( HttpServletRequest request,
                           String repositoryName,
                           String workspaceName ) throws RepositoryException {
    Repository repository = getRepository(repositoryName);

	SecurityContext context = new ServletSecurityContext(request);
	Credentials credentials = new SecurityContextCredentials(context);
    return repository.login(credentials, workspaceName);
}

The getSession(...) method is used by most of the REST server methods to access the JCR repository and return results as needed.

Finally, the shutdown() method signals that the web context is being undeployed and the JCR repository should shutdown and clean up any resources that are in use.