Skip to content

Let's Build DevDays US 2020

James Agnew edited this page Jun 16, 2020 · 7 revisions

Let's Build - DevDays US 2020

This page contains instructions for the DevDays US 2020 Let's Build session. In this exercise we will take a few key features of the HAPI FHIR JPA Server for a spin.

First, check out the project here: https://github.com/hapifhir/hapi-fhir-jpaserver-starter (or if you already have, perform a git pull to ensure you have the latest code.

You will also need a Java JDK with version 8 or 11 (others may work but we recommend one of these two as they are the focus of our testing). You can get JDK 11 here: https://adoptopenjdk.net/

Finally, you will need Apache Maven. You can get Maven here: https://maven.apache.org/download.cgi

Start the Server Up

Out of the box, HAPI FHIR JPA uses an embedded database called H2. H2 is a nice database for testing things, because it doesn't require you to install anything. We will use it for this session, but if you are building a server that you are going to put real data on you will definitely want to swap this for a production grade database such as Postgresql.

Before doing anything, we'll run the following command to clean out our environment. Be warned that this command wipes all stored data in the server so don't run it again unless you want to start over:

mvn clean

To start the JPA server, issue the following command:

mvn jetty:run

You will see some messages in the console while it starts. After a few moments, it should settle down and you'll see a message like this:

[INFO] Started ServerConnector@3f867060{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
[INFO] Started @17521ms
[INFO] Started Jetty Server

Seeding Some Data

Let's add two patients to our server. First, we'll do a FHIR Update with Client Assigned ID to create a patient with the ID Patient/A. This is a useful feature of FHIR that allows the client to pick the ID assigned to the resource:

{
	"resourceType": "Patient",
	"id": "A",
	"identifier": [ {
		"system": "http://springfieldhospital.edu/patient",
		"value": "3894746"
	} ],
	"name": [ {
		"family": "Simpson",
		"given": [ "Homer", "J" ]
	} ],
	"gender": "male",
	"birthDate": "1956-05-12"
}

Next, let's create a Patient/B

{
	"resourceType": "Patient",
	"id": "B",
	"identifier": [ {
		"system": "http://springfieldhospital.edu/patient",
		"value": "1234567"
	} ],
	"name": [ {
		"family": "Simpson",
		"given": [ "Marge" ]
	} ],
	"gender": "female",
	"birthDate": "1956-10-01"
}

Now, let's create a few Observations. We'll use a FHIR Transaction for this, in order to create several resources at the same time.

{
	"resourceType": "Bundle",
	"type": "transaction",
	"entry": [
		{
			"resource": {
				"resourceType": "Observation",
				"status": "final",
				"code": {
					"coding": [
						{
							"system": "http://loinc.org",
							"code": "789-8",
							"display": "Erythrocytes [#/volume] in Blood by Automated count"
						}
					]
				},
				"subject": {
					"reference": "Patient/A"
				},
				"effectiveDateTime": "2020-06-16T12:00:00-04:00",
				"valueQuantity": {
					"value": 4.12,
					"unit": "10 trillion/L",
					"system": "http://unitsofmeasure.org",
					"code": "10*12/L"
				}
			},
			"request": {
				"method": "POST",
				"url": "Observation"
			}
		},
		{
			"resource": {
				"resourceType": "Observation",
				"status": "final",
				"code": {
					"coding": [
						{
							"system": "http://loinc.org",
							"code": "11557-6",
							"display": "Carbon dioxide in blood"
						}
					]
				},
				"subject": {
					"reference": "Patient/B"
				},
				"valueQuantity": {
					"value": 6.2,
					"unit": "kPa",
					"system": "http://unitsofmeasure.org",
					"code": "kPa"
				}
			},
			"request": {
				"method": "POST",
				"url": "Observation"
			}
		}
	]
}

You may want to add some more data beyond what we are showing here.

Try it out

Let's try a few searches for data. A couple of ideas are here, but you should play around and try others:

Adding A Simple Interceptor

Let's start with an easy interceptor: the Request Validating interceptor. You can see documentation for this interceptor here: https://hapifhir.io/hapi-fhir/docs/interceptors/built_in_server_interceptors.html#validation-request-and-response-validation

Adding this interceptor is easy because the JPA Starter project has a configuration option that handles this. In your IDE, open the file hapi.properties and look for the following line:

validation.requests.enabled=false

Change the value to true, and restart your server by hitting Control-C in the terminal window where you started it and then rerunning mvn jetty:run.

Your server is now set to validate incoming requests and will reject requests that fail validation. Let's test this out.

{
	"resourceType": "Patient",
	"name": [ {
		"family": "van Houten",
		"given": [ "Milhouse" ]
	} ],
	"gender": "male",
	"age": "12"
}

This request should fail, because we're using an invalid property ("age"). Try correcting this to the more correct "birthDate" element.

Adding a Security Interceptor

Adding validation is easy. Let's try something harder: an AuthorizationInterceptor. This interceptor lets you declare security rules in your system. You can learn about it here: https://hapifhir.io/hapi-fhir/docs/security/authorization_interceptor.html

To add a security interceptor, open the JpaRestfulServer.java class in your IDE.

In this class you will see a method called initialize(). You can add any customization to the HAPI FHIR server that you want to this method.

Add a simple authorization interceptor by adding the following code to the initialize() method:

    AuthorizationInterceptor authInterceptor = new AuthorizationInterceptor(){
      @Override
      public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
        return new RuleBuilder()
          .allow().read().resourcesOfType("Patient").inCompartment("Patient", new IdType("Patient/A")).andThen()
          .allow().read().resourcesOfType("Observation").inCompartment("Patient", new IdType("Patient/A")).andThen()
          .denyAll().andThen()
          .build();
      }
    };
    registerInterceptor(authInterceptor);

Note what this code is doing: We are creating a simple rule that allows read-only access to Patient/A and any data in Patient/A's "compartment" (meaning any data that belongs to that patient, such as Observations where that patient is the subject).

Try the following two read operations to see your security interceptor in action:

Multitenant Server

Now let's try building a multitenant server.

First, shut down your server (control-C) and open JpaRestfulServer.java. Now comment out your authorization interceptor.

AuthorizationInterceptor does work with multitenant servers, but it is probably easier to get the tenancy part working first.

partitioning.multitenancy.enabled=false

Change the setting to true and start the server again by running mvn jetty:run. Now let's create some partitions, which will serve as our tenants.

{
  "resourceType": "Parameters",
  "parameter": [ {
    "name": "id",
    "valueInteger": 1
  }, {
    "name": "name",
    "valueCode": "TENANT-A"
  } ]
}
{
  "resourceType": "Parameters",
  "parameter": [ {
    "name": "id",
    "valueInteger": 2
  }, {
    "name": "name",
    "valueCode": "TENANT-B"
  } ]
}

Now let's seed two patients, each into their own tenant.

{
	"resourceType": "Patient",
	"id": "AA",
	"identifier": [ {
		"system": "http://springfieldhospital.edu/patient",
		"value": "4634252"
	} ],
	"name": [ {
		"family": "McClure",
		"given": [ "Troy" ]
	} ],
	"gender": "male",
	"birthDate": "1956-05-12"
}
{
	"resourceType": "Patient",
	"id": "BB",
	"identifier": [ {
		"system": "http://springfieldhospital.edu/patient",
		"value": "4634252"
	} ],
	"name": [ {
		"family": "McClure",
		"given": [ "Troy" ]
	} ],
	"gender": "male",
	"birthDate": "1956-05-12"
}

We have now created 2 patients, each in a different tenant.

Now let's search one of the tenants:

http://localhost:8080/hapi-fhir-jpaserver/fhir/TENANT-A/Patient

As you can see, you only see one of the two patients.

Next Steps

  • Try mixing the AuthorizationInterceptor and multitenancy.