Chapter 9. Accessing ModeShape Remotely

ModeShape provides a pair of ways to connect from remote clients: a WebDAV interface and a RESTful interface. This chapter details the capabilities of both as well as the configuration required to use each.

Note

Although the WebDAV and REST servers are treated separately here, many of the configuration parameters are the same. This is because both share a fair amount of common code and have been designed to be able to be deployed simultaneously on the same server or even within the same web archive.

Note

The WebDAV and REST servers described here exist for easy use, though they may need to be customized and WAR files reassembled to fit your particular application server and configuration. ModeShape's JBoss EAP is one such customization, with a number of additional components built specifically for the JBoss Application Server environment.

9.1. The ModeShape WebDAV Server

ModeShape provides a WebDAV server interface to its JCR implementation to ease integration with client applications. The WebDAV server maps some of the content nodes (by default, nodes with a primary type of nt:file) to WebDAV resources and the other nodes to WebDAV folders. This allows any WebDAV client to navigate through the content repository to store files in a given location, as well as to create or delete nodes in the repository.

9.2. The ModeShape REST Server

Metadata Repository provides a RESTful interface to its JCR implementation that allows HTTP-based access and updating of content.
The REST server is deployed in /modeshape-rest.

9.3. Supported Resources and Methods

The REST server currently supports the URIs and HTTP methods described below.

Table 9.1. Supported URIs for the Metadata Repository REST Server

URI Pattern Description HTTP Methods
http://<host>:<port>/modeshape-rest Returns a list of accessible repositories GET
http://<host>:<port>/modeshape-rest/{repositoryName} Returns a list of accessible workspaces within that repository GET
http://<host>:<port>/modeshape-rest/{repositoryName}/{workspaceName} Returns a list of available operations within the workspace GET
http://<host>:<port>/modeshape-rest/{repositoryName}/{workspaceName}/item/{path} Accesses the item (node or property) at the path GET, POST, PUT, DELETE
http://<host>:<port>/modeshape-rest/{repositoryName}/{workspaceName}/query Executes the query in the request body POST

9.4. Return a List of Accessible Repositories

A typical conversation might start with a request to the server to dynamically discover the available repositories.
GET http://www.example.com/modeshape-rest
This request would generate a response that mapped the names of the available repositories to metadata information about the repositories:
{
   "eds" : { 
      "repository" : 
      {
         "name" : "eds",
         "resources" : {  "workspaces":"/modeshape-rest/eds" }
      }
   }
}
The actual response would not be pretty-printed like the example, but the format would be the same. The name of the repository ("repository" URL-encoded) is mapped to a repository object that contains a name (the redundant "repository") and a list of available resources within the repository and their respective URIs. Note that Metadata Repository supports deploying multiple JCR repositories side-by-side on the same server, so this response could easily contain multiple repositories in a real deployment.

9.5. Return a List of Workspaces for a Repository

Once you know the name of an accessible repository, you can retrieve a list of its workspaces:
GET http://www.example.com/modeshape-rest/eds
This request (and all of the following requests) actually create a JCR session (javax.jcr.Session) to service the request and require that security be configured. The response looks similar to the following:
{
   "default" : {
      "workspace" : {
         "name" : "default",
         "resources" : { 
            "items":"/modeshape-rest/eds/default/items", 
            "query":"/modeshape-rest/eds/default/query"
         },
      }
   }
}
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 and the "query" resource builds the full URI needed to execute queries.

9.6. Access a Repository Item

Once you know the path to a repository's workspaces, you can retrieve the root item of the repository:
GET http://www.example.com/modeshape-rest/eds/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": "mode: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: mode: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/modeshape-rest/eds/default/items?mode:depth=2
Then the response would have contained details for the children of the root node as well.
{
   "properties": {
      "jcr:primaryType": "mode:root",
      "jcr:uuid": "163bc5e5-3b57-4e63-b2ae-ededf43d3445"
   },
   "children": {
      "jcr:system": {
         "properties": {"jcr:primaryType": "mode:system"},
         "children": ["mode:namespaces"]
      }
   }
}

9.7. Modify Repository Content

It is also possible to use the RESTful API to add, modify and remove repository content. Removal is simple - a DELETE request with no body returns a response with no body.
DELETE http://www.example.com/modeshape-rest/eds/default/items/path/to/deletedNode
Adding content 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/modeshape-rest/eds/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 JSON-encoded response from the request will contain the node that you just posted, including any autocreated properties and child nodes.
If you do not need this information, add mode:includeNode=false as a query parameter to your URL.
POST http://www.example.com/modeshape-rest/eds/default/items/newNode?mode:includeNode=false

{
   "properties": {
      "jcr:primaryType": "nt:unstructured",
      "jcr:mixinTypes": "mix:referenceable",
      "someProperty": "foo"
   },
   "children": {
      "newChildNode": {
         "properties": {"jcr:primaryType": "nt:unstructured"}
      }
   }
}
This will instruct the REST server to only return the path of the newly-created node in the response.
The PUT method allows for updates of nodes and properties. If the URI points to a property, the body of the request must contain the new JSON-encoded value for the property, which includes the property name, allowing proper determination of whether the values are binary; refer to Section 9.10, “Binary Properties”.
PUT http://www.example.com/modeshape-rest/eds/default/items/some/existing/node/someProperty

{
   "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/modeshape-rest/eds/default/items/some/existing/node

{
   "someProperty": "foobar",
   "someOtherProperty": "newValue"
}
The JSON request can even contain a properties container:
PUT http://www.example.com/modeshape-rest/eds/default/items/some/existing/node

{
   "properties": {
      "someProperty": "foobar",
      "someOtherProperty": "newValue"
   }
}
A subgraph can be updated all at once using a PUT against a URI of the top node in the subgraph. Note that, in this case, every node in the subgraph must be provided in the JSON request (any node not in the request will be removed). This method will attempt to set all of the properties to the new values as specified in the JSON request, plus any descendant node in the JSON request that does not reflect an existing node will be created while any existing node not reflected in the JSON request will be removed. Any specifications of jcr:primaryType are ignored if the node already exists. In other words, the request only needs to contain the properties that are changed. Of course, if a node is being added, all of its properties need to be included in the request.
Here is an example:
PUT http://www.example.com/modeshape-rest/eds/default/items/some/existing/node

{
   "properties": {
      "jcr:primaryType": "nt:unstructured",
      "jcr:mixinTypes": "mix:referenceable",
      "someProperty": "foo"
   },
   "children": {
      "childNode": {
         "properties": {"jcr:primaryType": "nt:unstructured"}
      }
   }
}
This will update the existing node at /some/existing/node with the specified properties, and ensure that it contains one child node named childNode. Note that the body of this request is identical in structure to that of the POST requests.

9.8. Query the Content Repository

Queries can be executed through the REST interface by sending a POST request to the query URI with the query statement in the body of the request. The query language must be specified by setting the appropriate MIME type.
All queries for a given workspace are posted to the same URI and the request body is not JSON-encoded.
POST http://www.example.com/modeshape-rest/eds/default/query

/a/b/c/d[@foo='bar']
Assuming that the above request was a POST with a content type of application/jcr+xpath, a response would be generated that consisted of a JSON object that contained a property named rows. The rows property would contain an array of rows with each element being a JSON object that represented one row in the query result set.
{
   "types": {
      "someProperty": "STRING",
      "someOtherProperty": "BOOLEAN",
      "jcr:path": "STRING",
      "jcr:score": "DECIMAL"
   },
   "rows": {
      {
         "someProperty": "foobar",
         "someOtherProperty": "true",
         "jcr:path" : "/a/b/c/d",
         "jcr:score" : 0.9327
      },
      {
         "someProperty": "localValue",
         "someOtherProperty": "false",
         "jcr:path" : "/a/b/c/d[2]",
         "jcr:score" : 0.8143
      }
   }
}
The JSON object in the response also contains a types property. The value of the types property is a JSON object that maps column names to their JCR type.

9.9. Query Content Types

Table 9.2. Query Content Types for the Metadata Repository REST Server

Query Language Content Type
XPath application/jcr+xpath
JCR-SQL application/jcr+sql
JCR-SQL2 application/jcr+sql2
Full Text Search application/jcr+search

If no content type is specified or the content type for the request is not one of the content types listed above, the request will generate a response code of 400 (BAD REQUEST).

9.10. Binary Properties

Binary property values are included in any of the responses or requests, but are represented as string values containing the Base64 encoding of the binary content. Any such property is explicitly annotated such that /base64/ is appended to the property name. This makes it very clear to the client and service which properties are encoded, allowing them to properly decode the values before use. The /base64/ suffix cannot be used in a real property name without escaping.
Here's an example of a node containing a jcr:primaryType property with a single string value, a jcr:uuid property with another single UUID value, another options property that has two integer values, and a fourth content property that has a single binary value:
{
   "properties": {
      "jcr:primaryType": "nt:unstructured",
      "jcr:uuid": "163bc5e5-3b57-4e63-b2ae-ededf43d3445"
      "options": [ "1", "2" ]
      "content/base64/": 
      "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu
dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="
   },
}
All values of a property will always be Base64 encoded if at least one of the values is binary. If there are multiple values, then they will be separated by commas and will appear within brackets ([ ]) like other properties.

9.11. ModeShape REST Client API

The ModeShape REST Client API provides a way of using the ModeShape REST web service to publish (upload) and unpublish (delete) files from ModeShape repositories. Java objects open the HTTP connection, create the HTTP request URLs, attach the payload associated with PUT and POST requests, parse the HTTP JSON response back into Java objects, and close the HTTP connection.
The eds/modeshape/client/modeshape-client.jar JAR file contains the ModeShape REST Client API.
The org.modeshape.web.jcr.rest.client.domain package contains the following required objects:
  • Server - hosts one or more ModeShape JCR repositories,
  • Repository - a ModeShape JCR repository containing one or more workspaces, and
  • Workspace - a ModeShape JCR repository workspace.
Publish and unpublish operations are performed using a class that implements the org.modeshape.web.jcr.rest.client.IRestClient interface.

Note

The only included implementation of IRestClient is org.modeshape.web.jcr.rest.client.json.JsonRestClient which uses JSON as its data format.

9.12. Publish a File Using the REST Client API

// Setup POJOs
Server server = new Server("http://localhost:8080", "username", "password");
Repository repository = new Repository("repositoryName", server);
Workspace workspace = new Workspace("workspaceName", repository);

// Publish
File file = new File("/path/to/file");
IRestClient restClient = new JsonRestClient();
Status status = restClient.publish(workspace, "/workspace/path/", file);

if (status.isError() 
{
   // Handle error here
}
Successfully executing the above code results in the creation of a JCR folder node (nt:folder) for each segment of the workspace path (if the folder didn't already exist). Also, a JCR file node (a node with primary type nt:file) is created or updated under the last folder node and the file contents are encoded and uploaded into a child node of that file node.
Refer to the quickstart example in the SOA_ROOT/jboss-as/samples/quickstarts/modeshape_helloworld_publish/ directory.

9.13. Repository Providers

The ModeShape REST and ModeShape WebDAV servers can provide access to other JCR repositories by implementing the org.modeshape.web.jcr.spi.RepositoryProvider interface.
There are four methods defined by the RepositoryProvider interface: startup, getJcrRepositoryNames, getSession and shutdown. When org.modeshape.web.jcr.ModeShapeJcrDeployer starts, it will call the RepositoryProvider startup method which will load the configuration (for example, from a web.xml file) and initialize the repository.
As an example, here's the ModeShape 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();
}
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 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 repository.
public Set<String> getJcrRepositoryNames() {
    return new HashSet<String>(jcrEngine.getRepositoryNames());
}
The ModeShape 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.