Server

HOWTO: Contribute to the REST API

Updated: March 18, 2024

The Nuxeo REST API provides many endpoints. Moreover, it has been designed to be highly pluggable allowing to contribute your own endpoint whenever required. Similarly, you can contribute your own web adapter. Let's first see how the REST API works, then how it can be extended.

How the REST API Works

The REST API is coded in the nuxeo-rest-api-server Maven module. It is a WebEngine module that:

  • Defines the APIRoot instance, the resource class serving requests for /nuxeo/api/v1 through the javax.ws.rs.Path annotation:

    @Path("/api/v1{repo : (/repo/[^/]+?)?}")
    
  • Provides these resources as Web Objects.

  • Allows the Writers and Readers to marshal and unmarshal these resources.

As an example, let’s have a look at the directory endpoint and see how it resolves a GET request on /nuxeo/api/v1/directory/continent/africa.

First, APIRoot retrieves the proper directory WebObject with:

@Path("/directory")
public Object doGetDirectory() {
    return newObject("directory");
}

The directory WebObject is made available by DirectoryRootObject:

@WebObject(type = "directory")
public class DirectoryRootObject extends DefaultObject {
    @Path("{directoryName}")
    public Object doGetDirectory(@PathParam("directoryName") String dirName){
         return newObject("directoryObject", dirName);
    }
}

This will resolve the directory name continent passed as a path parameter and serve the relevant DirectoryObject.

This DirectoryObject then resolves the africa directory entry to serve the relevant DirectoryEntryObject:

@Path("{entryId}")
public Object getEntry(@PathParam("entryId") final String entryId) {
    return withDirectorySession(directory, new DirectorySessionRunner<Object>()) {
        @Override
        Object run(Session session) throws ClientException {
            DocumentModel entry = session.getEntry(entryId);
            if (entry == null) {
                throw new WebResourceNotFoundException("Entry not found");
            }
            return newObject("directoryEntry", new DirectoryEntry(directory.getName(), entry));
        }
    });
}

This DirectoryEntryObject will finally return a DirectoryEntry entity:

@GET
public DirectoryEntry doGet() {
    return entry;
}

Last but not the least, this DirectoryEntry entity has to be serialized and unserialized in JSON. This can be respectively done by:

Extending the REST API: the Workflow Case

As an example, let's take the REST API endpoints allowing to initiate and run workflows. Documentation for these endpoints is available in the API Playground:

  • Workflow endpoint.
  • WorkflowModel endpoint.
  • Task endpoint.

Main Principle: the Fragment Bundle

The most important point is to understand how we can extend the existing Nuxeo REST API Server Maven module from another module in order to add new endpoints available under the existing /nuxeo/api/v1 resource path.

This is possible thanks to the OSGI fragments. As defined in the OSGI documentation:

A Bundle fragment, or simply a fragment, is a bundle whose contents are made available to another bundle (the fragment host).

In the case of the Workflow REST API, we added a new bundle called nuxeo-routing-rest-api which is a fragment of the host bundle rest-api bundle-server. To achieve this, we simply needed to declare the Fragment-Host in the MANIFEST.MF of the fragment bundle as follow:

Manifest-Version: 1.0
Bundle-ManifestVersion: 1
Bundle-Name: nuxeo-routing-rest-api
Bundle-SymbolicName: org.nuxeo.ecm.platform.restapi.server.routing;singleton:=true
Fragment-Host: org.nuxeo.ecm.platform.restapi.server
Bundle-Vendor: Nuxeo
Bundle-Version: 1.0.0

This causes the classes included in the fragment bundle to be deployed as part of the host bundle.

This also causes all of the other components in the fragment bundle not to be deployed. Thus, any endpoint or web adapter class you add, respectively annotated with @WebObject or @WebAdapter, need to be in a standalone bundle, not in the same bundle as extension point XML contributions for instance.

Contributing a REST API Endpoint

A REST API endpoint is defined by a class annotated with @WebObject.

In the fragment bundle, all the @WebObject annotated classes need to be placed in a package with a name prefixed with the package name of the APIRoot class, i.e. org.nuxeo.ecm.restapi.server.jaxrs.

In the case of the Workflow REST API, the @WebObject annotated classes are grouped within the org.nuxeo.ecm.restapi.server.jaxrs.routing package.

As a direct result and thanks to a routing method, the @WebObject annotated classes located in the fragment bundle will be discovered and made available under the /nuxeo/api/v1 resource path as well as to their readers and writers. For instance, the WorkflowObject from the nuxeo-routing-rest-api fragment bundle is automatically detected and available under the /nuxeo/api/v1/workflow path.

@WebObject(type = "workflow")
@Produces(MediaType.APPLICATION_JSON)
public class WorkflowObject extends DefaultObject {

    @GET
    @Path("{workflowInstanceId}")
    public DocumentRoute getWorkflowInstance(@PathParam("workflowInstanceId") String workflowInstanceId) {
        DocumentModel workflowInstance;
        try {
            workflowInstance = getContext().getCoreSession().getDocument(new IdRef(workflowInstanceId));
            return workflowInstance.getAdapter(DocumentRoute.class);
        } catch (NuxeoException e) {
            e.addInfo("Can not get workflow instance with id: " + workflowInstanceId);
            throw e;
        }
    }
}

Contributing a Web Adapter

A web adapter is defined by a class annotated with @WebAdapter.

In the exact same way as for a new endpoint, all the @WebAdapter annotated classes need to be placed in a package with a name prefixed with org.nuxeo.ecm.restapi.server.jaxrs.

In the case of the Workflow REST API, the @WebAdapter annotated classes are grouped within the org.nuxeo.ecm.restapi.server.jaxrs.routing.adapter package.

As a direct result, the @WebAdapter annotated classes located in the fragment bundle will be discovered and made available. For instance, the WorkflowAdapter from the nuxeo-routing-rest-api fragment bundle is automatically detected and available under the /nuxeo/api/v1/path/my/doc/path/@workflowAdapter path.

@WebAdapter(name = WorkflowAdapter.NAME, type = "workflowAdapter")
public class WorkflowAdapter extends DefaultAdapter {

    public static final String NAME = "workflow";

    @GET
    public List<DocumentRoute> doGet() {
        DocumentModel doc = getTarget().getAdapter(DocumentModel.class);
        return Framework.getService(DocumentRoutingService.class).getDocumentRelatedWorkflows(doc,
                getContext().getCoreSession());
    }
}