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 thejavax.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:
DirectoryEntryJsonWriter based on
javax.ws.rs.ext.MessageBodyWriter
.DirectoryEntryJsonReader based on
javax.ws.rs.ext.MessageBodyReader
.
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());
}
}