This article covers a Bloomreach Experience Manager version 13. There's an updated version available that covers our most recent release.

Repository JAX-RS Service

Bloomreach Experience Manager provides several mechanisms to expose REST APIs, read this overview to see which best fits your use case.

Introduction

Goal

Use the Repository JAX-RS Service to define Repository REST endpoints.

Background

Bloomreach Experience Manager's content repository provides a RepositoryJaxrsService which supports dynamic adding and removing of JAX-RS REST Application endpoints. These Application endpoints can be accessed through a corresponding RepositoryJaxrsServlet which is included by default in projects created using the Maven archetype. Several Bloomreach Experience Manager define such REST endpoints and implementation projects can add their own.

Default Configuration

By default, the hippo-repository-jaxrs dependency is included as runtime-scoped dependency in the CMS/platform web application.

The RepositoryJaxrsServlet is configured in the CMS application web.xml under /ws/<endpoint address>:

  <servlet>
    <servlet-name>RepositoryJaxrsServlet</servlet-name>
    <servlet-class>org.onehippo.repository.jaxrs.RepositoryJaxrsServlet</servlet-class>
    <load-on-startup>6</load-on-startup>
  </servlet>

  ...

  <servlet-mapping>
    <servlet-name>RepositoryJaxrsServlet</servlet-name>
    <url-pattern>/ws/*</url-pattern>
  </servlet-mapping>

By default, REST endpoints registered at the RepositoryJaxrsService are secured with basic authentication via the Hippo Repository and require a valid repository (CMS) username and password.

Create a Custom Repository REST Endpoint

Configure Maven Module

When developing your own repository REST endpoint, use a separate Maven module and add a dependency with scope 'provided' to its pom.xml:

    <dependency>
      <groupId>org.onehippo.cms7</groupId>
      <artifactId>hippo-repository-jaxrs</artifactId>
      <scope>provided</scope>
    </dependency>

Define and Register a Repository REST Endpoint

The hippo-repository-jaxrs jar dependency provides a Java RepositoryJaxrsEndpoint builder class which is used to define and register a JAX-RS Application instance (or just one or more JAX-RS Resources) as endpoint with the RepositoryJaxrsService at a specific (endpoint) address. The typical use-case for a Repository REST endpoint is a repository managed daemon module providing a REST API to interact with and manage it. A trivial "Hello World" example of such a daemon module would be:

package com.example.hello;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

import org.onehippo.repository.jaxrs.RepositoryJaxrsEndpoint;
import org.onehippo.repository.jaxrs.RepositoryJaxrsService;
import org.onehippo.repository.modules.DaemonModule;

public class HelloModule implements DaemonModule {

    @Override
    public void initialize(final Session session) throws RepositoryException {
        RepositoryJaxrsService.addEndpoint(
          new RepositoryJaxrsEndpoint("/hello").singleton(new HelloResource()));
    }

    @Override
    public void shutdown() {
        RepositoryJaxrsService.removeEndpoint("/hello");
    }

    public static class HelloResource {
        @Path("/")
        @GET
        public String sayHello(@QueryParam("name") String name) {
            return "Hello " + name +"!";
        }
    }
}

When this example daemon module is deployed with the CMS, it will automatically be started by the Repository and then itself dynamically register its JAX-RS HelloResource endpoint at address:  /hello. This endpoint then can be accessed through for example  http://localhost:8080/cms/ws/hello?name=world which then will return (after authentication with a valid CMS username and password): Hello world!

Build a RepositoryJaxrsEndpoint

A RepositoryJaxrsEndpoint builder instance is created with the root address for the target endpoint, like for example "/hello". Note that the RepositoryJaxrsService will fail to add an endpoint on an address already in use, so make sure to choose and claim a unique and dedicated address for your endpoint. A endpoint address must start with a "/", which will be ensured by the builder itself by automatically prefixing a "/" if needed. Such a builder instance then can further be configured with either a JAX-RS Application instance like:

RepositoryJaxrsEndpoint endpoint = 
   new RepositoryJaxrsEndpoint("/").app(myJaxrsApplication);

or with separate, multiple if desired, JAX-RS resource, provider or feature root classes and/or singletons (corresponding with those provided by an Application instance), like:

RepositoryJaxrsEndpoint endpoint =
   new RepositoryJaxrsEndpoint("/")
      .rootClass(MyRootClass.class)
      .rootClass(OtherRootClass.class)
      .singleton(mySingletonResource)
      .singleton(otherSingletonResource);

Build an Apache CXF Specific CXFRepositoryJaxrsEndpoint

The RepositoryJaxrsService uses Apache CXF as JAX-RS engine, and Apache CXF supports additional extensions through so called CXF Interceptors. With a CXFRepositoryJaxrsEndpoint extending the RepositoryJaxrsEndpoint, such CXF specific Interceptors can be configured as well, like:

RepositoryJaxrsEndpoint endpoint =
   new CXFRepositoryJaxrsEndpoint("/")
      .inInterceptor(myInInterceptor)
      .outInterceptor(myOutInterceptor)
      .singleton(mySingletonResource);

Customize or Override the REST Endpoint Authentication

By default, the RepositoryJaxrsService will configure every REST endpoint to be basic authenticated against the repository, using the provided username and password to (only) login to the repository. The authentication (and authorization, see next section) handling is configurable and overridable, per REST endpoint when using a CXFRepositoryJaxrsEndpoint builder. The authentication, and optionally authorization, is handled by a custom CXF JAXRSInvoker providing pre/post processing of a request invocation. The default authentication is provided by the AuthenticatingRepositoryJaxrsInvoker, which enforces a repository login before proceding with the request handling. The CXFRepositoryJaxrsEndpoint builder allows configuring a custom JAXRSInvoker, thereby providing complete freedom for custom request pre/post processing, including a custom authentication/authorization implementation:

RepositoryJaxrsEndpoint endpoint =
   new CXFRepositoryJaxrsEndpoint("/")
      .invoker(new AuthenticatingRepositoryJaxrsInvoker()) // the default
      .singleton(mySingletonResource);

Note that configuring a null invoker will result in the default invoker being set, it will not disable the authentication. If that is needed, you should configure the default (CXF) JaxrsInvoker instead:

RepositoryJaxrsEndpoint endpoint =
   new CXFRepositoryJaxrsEndpoint("/")
      .invoker(new org.apache.cxf.jaxrs.JAXRSInvoker()) // default CXF JARSInvoker
      .singleton(mySingletonResource); 

Configure REST Endpoint Authorization

Besides enforcing authentication a REST endpoint can also be configured to require additional, repository based, authorization. The RepositoryJaxrsEndpoint builder provides a fluent  authorized(String authorizationNodePath, String authorizationPermission) method through which a specific Hippo Repository permission can be specified to be checked against a specific node path, for the authenticated user. The RepositoryJaxrsService.HIPPO_REST_PERMISSION is a convenient and predefined (and bootstrapped) permission for this purpose:

public static final String HIPPO_REST_PERMISSION = "hippo:rest";

When configuring REST endpoint(s) for a repository daemon module, you can for example use the daemon module path for authorization, like:

package org.example.hello;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

import org.onehippo.repository.jaxrs.RepositoryJaxrsEndpoint;
import org.onehippo.repository.jaxrs.RepositoryJaxrsService;
import org.onehippo.repository.modules.AbstractReconfigurableDaemonModule;

import static org.onehippo.repository.jaxrs.RepositoryJaxrsService.HIPPO_REST_PERMISSION;

public class HelloModule extends AbstractReconfigurableDaemonModule {

    private String modulePath;

    @Override
    protected void doConfigure(final Node moduleConfig) throws RepositoryException {
        modulePath = moduleConfig.getParent().getPath();
    }
    
    @Override
    public void initialize(final Session session) throws RepositoryException {
        RepositoryJaxrsService.addEndpoint(
           new RepositoryJaxrsEndpoint("/hello")
              .singleton(new HelloResource())
              .authorized(modulePath, HIPPO_REST_PERMISSION);
    }
...

How to setup and configure a security domain using the HIPPO_REST_PERMISSION on the daemon module path or some other path, which grants selected users access to its REST endpoint is described at  Repository Authorization and Permissions. The hippo-repository-jaxrs module predefines and bootstraps a restuser role having the HIPPO_REST_PERMISSION, which can be used for setting up such a security domain for authorizing a Repository REST endpoint. The authorization handling is performed by a AuthorizingRepositoryJaxrsInvoker, which extends the default AuthenticatingRepositoryJaxrsInvoker. Note that when overriding the endpoint JAXRSInvoker with a custom invoker, handling authentication and authorization also becomes the responsibility of this custom invoker.

Two notes about the the repository authorization model:

  • The repository authorization model is implemented such that checking for a permission on a path that does not exist for any user always succeeds. In other words, it is highly recommended to use a path that is guaranteed to exist for the authorization check. For Repository daemon modules, best practise is to use the modules configuration root for this.
  • A security domain can be configured in such a way that users do have the HIPPO_REST_PERMISSION privilege, but do not have read access for a certain path.
Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?