PEACH Reality API

Author: Fraser Savage.

Feature Overview

The API is mostly feature complete, with core functionality set out at the start of the project implemented and made use of by multiple web applications. As a high level feature overview, two sets of functionality are implemented:

  1. The representation of users in the system and the operations which create, read, update and delete them from it.
  2. The representation of Holographic Patient Cases in the system and the operations which create, read, update and delete from it.

It is worth noting that all usage of the API requires the user to have authenticated with it and show evidence that they are authorised to perform their request. This was built into the system due to the nature of the field - if a system such as ours was to transition from Proof of Concept to the health service then having a base security & permissions system that can be extended to follow regulations is a requirement.

Users

Users are created in the API by the following model:

UserBlueprint {
  userEmail (string): User's login id (must be valid email) ,
  password (string): User's password (length must be 8-32) ,
  fullName (string): User's full name (max length is 32) ,
  organisationName (string): Organisation the user belongs to ,
  organisationRole (string, optional): User's role within the organisation ,
  accessLevel (string, optional): User access level = ['USER', 'ADMIN']
}

With the addition of a UUID which is used internally to refer to users (allowing for changes to user email without issue). The unique id is a string of length 36 which makes use of the Java platform's random UUID generator. While the password here in the representation is in plain-text passwords are hashed with using 'PBKDF2 with HmacSHA512' and then salted.

There are a number of API operations which are implemented for working with the user representation in the system:

  • Log into the system, providing a one-time use authorization token which is associated with that user's permissions
  • Retrieve a list of users from the system, displaying their email and name
  • Create a new user in the system by providing the above blueprint
  • Retrieve the user representation from the system (minus the password & UUID)
  • Update the the current user using the above blueprint
  • Update the of another user by their email using the above blueprint (provided that adequate authorization is provided)
  • Delete the user from the system by email (again, provided adequate authorization is provided)

Holographic Patient Cases

Holographic Patient Cases (HPCs) are the model which we use to represent the patient cases of doctors and surgeons. Storing these cases in the API allows for real-time collaboration using whichever tools have been built to consume the API as clients (currently HoloLens and some web apps).

HPCs are created in the API by the following at minimum:

CaseBlueprint {
  caseName (string): Name of the case = ['max=72'],
  caseDescription (string, optional): Brief overview of the case = ['max=255'],
  ownerEmail (string, optional): Case lead. If blank will set to user making request ,
  collaboratorEmails (Array[string], optional): List of collaborators on the case
}

This is then complemented automatically by a creation date, the date it was last updated (Partially implemented), a revision history (Partially implemented) and a unique id generated by the data persistence library.

In addition to the minimum representation for a HPC as above, HPCs may contain collections of the following entities:

  • 3D Models
  • DICOM files
  • Standard files

The first two are designed to be downloaded and used for utilisation in mixed reality through the HoloLens client, while the third is more for storing general documents for use from the webapp.

The API operations available for the minimum representation of a HPC are as follows:

  • Get a list of HPCs, showing id, name and owner
  • Create a new HPC by providing the blueprint above
  • Retrieve the minimum HPC representation from the system
  • Update a HPC using the blueprint above (with authorization)
  • Delete a HPC by id (with authorization)
  • Add/Remove collaborators to the HPC (enabling more authorized users)
  • Retrieve the revision history of HPC as a list of actions

In addition to this, the entities which can be added each have their own set of operations.

3D Models

3D Models are stored within HPCs, represented by a unique id, filename and a collection of data markers. The 3D Model itself is stored on Azure Blob Storage, to allow for scalable storage of large complex files. Data markers attached to a model are represented by a unique id, their (x,y,z) coordinates, a text note (size of a tweet) and a voice recording stored on azure.

The API operations available for 3D Models of a HPC are these:

  • Get a list of models for a HPC, showing id and name
  • Create a new model and add it to the HPC
  • Retrieve a model file for a model attached to a HPC
  • Update an existing model for a HPC
  • Delete a model for a HPC
  • Get a list of data markers for a model, showing id and coordinates
  • Create a new data marker for a model
  • Retrieve a data marker for a model
  • Retrieve the voice clip for a data marker
  • Update a data marker for a model
  • Set the voice clip for a data marker
  • Delete a data marker from a model

When updating a model, the data markers are not currently affected. The behaviour of that operation should be in future considered by the developers of both the API and any clients to it.

DICOMs

DICOMs are stored within HPCs as models are, but without the data markers.

The API operations available for DICOMs of a HPC are the below:

  • Get a list of DICOMs for a HPC, showing id and name
  • Create a new DICOM, adding it to a HPC
  • Retrieve a DICOM file for a dicom attached to a HPC
  • Update an existing DICOM for a HPC
  • Delete a DICOM for a HPC

Standard files

Standard files are stored within HPCs identically to DICOMs and also offer identical API operations.

Future Development Plans

Features which are set as a priority in the future include the following:

  • Completion of integration of neural network which converts CT scans to 3D models as part of the API standard pipeline.
  • Completion of revision history for HPCs (which would be achieved by adding function calls to the API resource endpoints to push strings onto the HPC history)
  • Refinement of data marker storage, using units which account for scaling of models within the HoloLens client
  • Improvements to the functional relationship between users and HPCs (such as safe delete, force delete for cases which reference users and vice versa)

How to Extend the API

To extend the API there are two key considerations:

  • Am I extending an existing endpoint?
  • Am I creating a new endpoint?

If extending an existing endpoint then, adding a new public Response method with JAX-RS capability to the relevant class is the appropriate way to do so (e.g. To extend the user endpoint you would implement a new method in the UserResource class). If you are extending an existing endpoint with multiple nested endpoints (see CaseResource and ModelResource) then adding a new public Response which returns a new subresource instance using ResourceContext is the appropriate manner. A subresource can be stored in its own class file (see CaseResource with CollaboratorResource, ModelResource, DicomResource and FileResource).

If creating a new endpoint then creating a new class which adheres the structure of CaseResource and UserResouce will work. To have the class scanned by providers it will need to be added to the resources set in PeachRealityApplication.

All extensions to the API should be documented appropriately using the swagger-core library [1] and named in the manner of <Loose Endpoint Name>Resource.

If you need to add a class which doesn't directly provide an endpoint, but powers operations there are a few alternative locations:

  • org.uclh.peach.reality.model: For entity models to be stored in the database
  • org.uclh.peach.reality.rest: For components related very directly to the REST APIs operations (i.e. serialisable objects in consumable, endpoint filters in filter and general utils and interfaces in the root of the rest directory.
  • org.uclh.peach.reality: For components such as database/storage delegates and application wide utils.

Note: If new persistence units are added to the source code they also need to be configure in the persistence.xml file

The application root configuration is stored in the PeachRealityApplication class, and that is where version information and configuration info is set for swagger core to serve. There are a number of config files in resources and webapp/WEB-INF which configure the JPA persistence layers, the application context root for Payara, Payara Micro and GlassFish (v3/4) deployments.

New extensions to the application should be covered (where possible without disproportionate effort) by unit and integration tests which can be placed in the test/java directory. This namely includes endpoint level functionality (response building) as opposed to JPA layer testing and Azure storage communication (which should still be tested functionally).

Implementation Overview

The API is implemented using Java EE 7 specification technologies, Jackson, Auth0 Java JWT, Apache Commons Validator and the Azure Blob Storage SDK. The core JEE technologies which are made use of are as follows:

  • EJB 3.2
  • JPA 2.1
  • JAX-RS 2.0 (through Jersey)
  • CDI 1.1
  • JTA 1.2
  • Servlet 3.1
  • JSONP

JSONP usage was largely replaced by Jackson due to its serialisation capabilities and it doesn't have mass amounts of checked exceptions in its function signatures (and already included through Auth0 Java JWT).

The REST resource layer loosely implements the Delegation pattern [2] to the JPA persistence layer, trying to limit the potential for code reuse by calling the entity transaction code through the Stateless Session Facade pattern [3]. Jersey also makes use of the MVC design pattern through its response model for resource classes [4], although I do not make use of the templating capabilities and instead combine entity serialisation with the Response class.

The API is bundled as a WAR containing it, the front end and Swagger UI [5]. Swagger UI is used to read the Swagger.json API spec produced from all annotations and display it in a nice, readable web UI. Additionally, the postgresql JPA library is bundled in.

This API is then deployed to Payara Micro [6] on a Ubuntu server running PostgreSQL [7].

Binary files are stored on a series of Azure Blob Store Containers, while the rest of data is stored in the PostgreSQL db.

Setting up the API

To set up the API, PostgreSQL must be set up on the server, with a user account and empty database for the API to use. Apache Maven [8] and JDK of at least version 7 must be installed as well. The persistence.xml file (which can be based on the example file in the repository should be configured to connect to postgress with the user details and database as earlier set up. For this stage of development (PoC testing) we have been using port 8080 (default Payara Micro port), although for production a web server such as Apache or NGINX should be set up as a reverse proxy from port 80 to 8080. Additionally a production deployment should have HTTPS configured and should URL rewrite all requests to port 80 to instead go to HTTPS at port 443.

To deploy the API once all of this is done, you can either use a script like we have used for the Continuous Deployment system for the testing server or run two commands. For illustrations the script used by the testing server is of the following format:

#!/bin/bash
echo 'Change dir to webapp'
cd peach-reality-webapp
git fetch origin
git pull origin testing
echo 'Copying meta files'
cp <configured web.xml> src/main/webapp/WEB-INF/
cp <configured persistence.xml> src/main/resources/META-INF/
echo 'Maven clean install'
mvn clean install -DskipTests
connection_string='<Azure Blob Storage Connection String>'
service='java -jar -Djwt.secret=<JWT Secret> -Djwt.issuer=<hostname/domain> -Dlocal=false -Dstorage.connection='"'"$connection_string"'"' /home/reality/payara-micro.jar --logToFile <Log file location>  --deploy target/peach-reality.war'
echo 'Kill payara micro with old deployment'
pkill -f 'payara-micro.jar'
echo 'Run payara micro and deploy new peach-reality'
echo $service
nohup $service &

Note: the script assumes that you already have the repository cloned to peach-reality-webapp. Note 2: The java -jar execution needs the jwt.secret, jwt.issuer and storage.connection properties to be set correctly. local can be omitted as the default is false. However if you are developing locally it needs to be set to true otherwise swagger.json will provide your public IP which may not let you use the docs when deployed. WARNING: The DskipTests flag is only used because our auto deployment system, Git Auto Deploy will only run the script if the GitLab CI successfully builds and passes tests. It is strongly recommended to not use this if you have no such system in place.

Alternatively, following the same process yields two commands:

  1. mvn clean install
  2. nohup 'java -jar -Djwt.secret=<JWT Secret> -Djwt.issuer=<hostname/domain> -Dlocal=false -Dstorage.connection='"'"$connection_string"'"' /home/reality/payara-micro.jar --logToFile <Log file location> --deploy target/peach-reality.war' &

The versions of Payara Micro which the API is tested on are 164 and 171.

Consuming the APIs

Consumption of the API endpoints is documented in detail through Swagger and by navigating to the /docs context a user of the API can see examples, endpoints available and data models. Docs are available through this badge at time of writing: docs. Note however, that the repository README file has a badge pointing towards the docs which should be kept updated at all times.

Testing Process

The API was developed in a test-driven manner where beneficial (see TDD [9]) with a much core functionality not related to Azure Blob Storage covered by unit & integration tests written using JUnit [10], Jersey Test Framework [11] and Mockito [12]. Arquillian was not utilised as planned due to the large amount of upfront configuration for less benefit. In addition both our web app front-end and another PEACH team's project were used to perform functional tests during development, namely where test coverage is non-existent (Azure Blob Store usage) or low (JPA heavy code).

As features were merged into the testing branch throughout development we used GitLab CI [13] to run automated builds & tests. If the CI runner passed all build and test stages then our Git Auto Deploy service would pull down the latest changes, build them and redeploy the API to our testing server. This meant that changes were available for functional testing and use in development as soon as they were committed to testing.

References

  1. Swagger Core
  2. Delegation
  3. Core J2EE Patterns - Session Facade
  4. Chapter 20. MVC Templates
  5. Swagger UI
  6. Payara Micro
  7. PostgreSQL: The world's most advanced open source database
  8. Maven - Welcome to Apache Maven
  9. TDD - Learn About Test Driven Development | Agile Alliance
  10. JUnit - About
  11. Chapter 26. Jersey Test Framework
  12. Mockito framework site
  13. GitLab CI