-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
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
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:
- PUT the following contents to the URL: http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient/A
{
"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
- PUT the following contents to the URL: http://localhost:8080/hapi-fhir-jpaserver/fhir/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.
- POST the following contents to the URL: http://localhost:8080/hapi-fhir-jpaserver/fhir/
{
"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.
Let's try a few searches for data. A couple of ideas are here, but you should play around and try others:
- http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient?name=Simpson
- http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient?gender=female
- http://localhost:8080/hapi-fhir-jpaserver/fhir/Observation?code=http://loinc.org|11557-6
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.
- POST the following contents to the URL: http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient
{
"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 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:
- http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient/A
- http://localhost:8080/hapi-fhir-jpaserver/fhir/Patient/B
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.
- Open hapi.properties and look for the following line:
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.
- POST the following payload to http://localhost:8080/hapi-fhir-jpaserver/fhir/DEFAULT/$partition-management-create-partition
{
"resourceType": "Parameters",
"parameter": [ {
"name": "id",
"valueInteger": 1
}, {
"name": "name",
"valueCode": "TENANT-A"
} ]
}
- POST the following payload to http://localhost:8080/hapi-fhir-jpaserver/fhir/DEFAULT/$partition-management-create-partition
{
"resourceType": "Parameters",
"parameter": [ {
"name": "id",
"valueInteger": 2
}, {
"name": "name",
"valueCode": "TENANT-B"
} ]
}
Now let's seed two patients, each into their own tenant.
- PUT the following payload to the following URL: http://localhost:8080/hapi-fhir-jpaserver/fhir/TENANT-A/Patient/AA
{
"resourceType": "Patient",
"id": "AA",
"identifier": [ {
"system": "http://springfieldhospital.edu/patient",
"value": "4634252"
} ],
"name": [ {
"family": "McClure",
"given": [ "Troy" ]
} ],
"gender": "male",
"birthDate": "1956-05-12"
}
- PUT the following payload to http://localhost:8080/hapi-fhir-jpaserver/fhir/TENANT-B/Patient/BB
{
"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.
- Try mixing the AuthorizationInterceptor and multitenancy.