Table of contents

Introduction

The Fast Healthcare Interoperability Resource, commonly known as FHIR, has quickly become one of the most popular protocols for joining disparate systems together, and holds great promise for the development of an application-based approach to interoperability and health information exchange.

If you haven’t heard of FHIR, I recommend you read the Healthcare on FHIR blogpost from my colleague Martin Kwee.
In this article we’ll focus on implementing the FHIR specification through the open source Java libraries called HAPI-FHIR. HAPI FHIR is a complete implementation of the HL7 FHIR standard for healthcare interoperability in Java.

HAPI Servers Types

HAPI FHIR provides several mechanisms for building FHIR servers. The appropriate choice depends on the specifics of what you are trying to accomplish.

Plain server

The HAPI FHIR Plain Server (often referred to as a Facade) is an implementation of a FHIR server against an arbitrary backend that you provide. In this mode, you write code that handles resource storage and retrieval logic, and HAPI FHIR takes care of:

  • HTTP Processing

  • Parsing / Serialization

  • FHIR REST semantics

This module was originally created at University Health Network (UHN) as a mechanism for placing a common FHIR layer on top of a series of existing data sources, including an electronic medical record system (EMR), an enterprise patient scheduling system, and a series of clinical data repositories. All of these systems existed long before FHIR was adopted at UHN and HAPI FHIR was created to make the process of adopting FHIR easier.

JPA Server

The HAPI FHIR JPA Server is a complete implementation of a FHIR server against a relational database. Unlike the Plain Server, the JPA server provides its own database schema and handles all storage and retrieval logic without any coding being required.

JAX-RS Server

The HAPI FHIR Plain Server (RestfulServer) is implemented as a standard JEE Servlet, meaning that it can be deployed in any compliant JEE web container. The JAX-RS module is a community-supported module that was not developed by the core HAPI FHIR team. Before deciding to use the HAPI FHIR JAX-RS module, please be aware that it does not have as complete of support for the full FHIR REST specification as the Plain Server.

For users in an environment where existing JAX-RS services have been created, it is often desirable to use JAX-RS for FHIR servers as well. HAPI FHIR provides a JAX-RS FHIR server implementation for this purpose.

Custom Plain server implementation

As you have read above, there are different server types for different needs. Since the JPA implementation is a fully out-of-the-box working implementation with a SQL database, I thought it would be a nice challenge to set up my own Plain server implementation using a NoSQL database. The remainder of this blogpost will cover some practical code snippets of how I’ve set up a FHIR RESTful server using Spring Boot and a MongoDB database.

Below a comparison between our custom developed Plain server and an out-of-the-box JPA server:

custom diagram

Out-of-the-box JPA server:

jpa diagram

Compared to the JPA server that is provided out-of-the-box, there are some minor differences but by and large they are similar. The differences are the database technologies and mapping technologies.

Modules

First of all, we will create a simple Spring Boot project and load the HAPI library using Maven. While there are many subprojects in the FHIR Codebase we only need 3 for the FHIR Façade or as the HAPI FHIR project lead James Agnew puts it, for HAPI Plain Server implementation.

  • Hapi-fhir-base
    • This is the core HAPI FHIR library and is always required in order to use the framework. It contains the context, parsers, and other support classes.
  • Hapi-fhir-structures
    • This module contains the StructureDefinitions, ValueSets, CodeSystems, Schemas, and Schematrons for a specific FHIR version.
  • Hapi-fhir-server
    • This module contains the HAPI FHIR Server framework, which can be used to develop FHIR compliant servers against your own data storage layer.

pom.xml

<dependencies>
    <dependency>
        <groupId>ca.uhn.hapi.fhir</groupId>
        <artifactId>hapi-fhir-structures-r4</artifactId>
        <version>5.2.0</version>
    </dependency>
    <dependency>
        <groupId>ca.uhn.hapi.fhir</groupId>
        <artifactId>hapi-fhir-base</artifactId>
        <version>5.2.0</version>
    </dependency>
    <dependency>
        <groupId>ca.uhn.hapi.fhir</groupId>
        <artifactId>hapi-fhir-server</artifactId>
        <version>5.2.0</version>
    </dependency>
</dependencies>

Server

The class in the code snippet below is going to be the heart of your custom HAPI FHIR implementation. We will be using Release 4 (R4) of the FHIR specification. Here you will configure most of your HAPI server settings. You can add security, select which resources your server will support, set defaults on the server to use XML or JSON and many more options. We will implement some more later in this post.

FhirRestfulServer.java

package com.example.fhirexample;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.RestfulServer;

import com.example.fhirexample.providors.ObservationProvidor;
import com.example.fhirexample.providors.PatientProvider;

import org.springframework.context.ApplicationContext;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import java.util.Arrays;

@WebServlet("/*")
public class FhirRestfulServer extends RestfulServer {

    private ApplicationContext applicationContext;

    FhirRestfulServer(ApplicationContext context) {

        this.applicationContext = context;
    }

    @Override
    protected void initialize() throws ServletException{
        super.initialize();
        setFhirContext(FhirContext.forR4());
    }
}

Now lets setup the Servlet Context. We’re using the standard setup of a Spring Boot application and register the FhirRestfulServer web servlet we’ve created earlier.

package com.example.fhirexample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class FhirExampleApplication{

    @Autowired
    private ApplicationContext context;


    public static void main(String[] args) {

        SpringApplication.run(FhirExampleApplication.class, args);
    }

    @Bean
    public ServletRegistrationBean ServletRegistrationBean() {
        ServletRegistrationBean registration= new ServletRegistrationBean(new FhirRestfulServer(context),"/*");
        registration.setName("FhirServlet");
        return registration;
    }
}

Once your server has started, open up your Postman and GET the following URL: http://localhost:8080/metadata. A capability statement will appear just like the screenshot below. A Capability Statement documents a set of capabilities (behaviors) of a FHIR Server for a particular version of FHIR that may be used as a statement of actual server functionality or a statement of required or desired server implementation. Congratulations you are now FHIR enabled!

postman

Resources

The PatientProvider is where the FHIR Patient behaviour is configured. HAPI uses annotations to indicate what kind of REST service a class method provides. The procedure “createPatient” in the diagram is annotated with @Create which indicates that it handles POST/create. This procedure then uses a PatientDAO (DAO - data access object) class which Spring Data uses to persist the Patient resource in MongoDB.

package com.example.fhirexample;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import com.example.fhirexample.dao.patient.PatientDAO;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.List;


@Component
public class PatientProvider implements IResourceProvider {

    @Autowired
    private FhirContext ctx;

    @Autowired
    private PatientDAO patientDao;
    
    @Override
    public Class<? extends IBaseResource> getResourceType() {
        return  Patient.class;
    }

    @Search
    public List<Resource> searchPatient(HttpServletRequest request,
                                        @OptionalParam(name= Patient.SP_BIRTHDATE) DateRangeParam birthDate,
                                        @OptionalParam(name = Patient.SP_FAMILY) StringParam familyName,
                                        @OptionalParam(name= Patient.SP_GENDER) StringParam gender,   
                                        @OptionalParam(name= Patient.SP_GIVEN) StringParam givenName,
                                        @OptionalParam(name = Patient.SP_IDENTIFIER) TokenParam identifier,
                                        @OptionalParam(name= Patient.SP_NAME) StringParam name,
                                        @OptionalParam(name = Patient.SP_RES_ID) TokenParam resid) {

        return patientDao.search(ctx, birthDate, familyName, gender, givenName, identifier, name);
    }

    @Read()
    public Patient read(@IdParam IdType theId) {

        return patientDao.read(ctx,theId);

    }

    @Create()
    public MethodOutcome createPatient(HttpServletRequest theRequest, @ResourceParam Patient patient) {

        MethodOutcome  method = new  MethodOutcome();
        method.setCreated(true);
        OperationOutcome  opOutcome = new  OperationOutcome();
        method.setOperationOutcome(opOutcome)
       
        return patientDao.create(ctx, patient);
    }

    @Search()
    public List<Resource> getAllPatients() {
        return patientDao.search(ctx);
    
    }

  

    @Delete()
    public void delete(@IdParam  IdType  theId) {
        
        patientDao.delete(ctx,theId);
    }
}

In the previous code snippet I want to highlight some important details like:

  • The MethodOutcome object must be returned on update and create methods. This object contains the identity of the created resource. On a delete and validate method you have a choice between void and MethodOutcome.
  • Operation outcomes are sets of error, warning and information messages that provide detailed information about the outcome of an attempted system operation. The operationOutcome can be used as a direct response from the server (or as a component of the response). For example when the method fails the operationOutcome can provide more information about the outcome. This can be used to provide meaningful error messages.

Now we configure our HAPI Server to support the Patient resource.

FhirRestfulServer.java

package com.example.fhirexample;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.RestfulServer;

import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import com.example.fhirexample.providors.ObservationProvidor;
import com.example.fhirexample.providors.PatientProvidor;

import org.springframework.context.ApplicationContext;
import org.springframework.web.cors.CorsConfiguration;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import java.util.Arrays;

@WebServlet("/*")
public class FhirRestfulServer extends RestfulServer {
    
    private ApplicationContext applicationContext;

    FhirRestfulServer(ApplicationContext context) {
        this.applicationContext = context;
    }

    @Override
    protected void initialize() throws ServletException {   

        super.initialize();
        setFhirContext(FhirContext.forR4());
        setResourceProviders(Arrays.asList(
            applicationContext.getBean(PatientProvider.class)));
    }
}

In this section we have shown you how to implement a Patient resource on a HAPI FHIR server.

Security

Security is a crucial part of setting up a server especially when it comes to sensitive data like health information. To easily facilitate this security concern, there are some out-of-the box features that HAPI provides to ensure the safeguarding of sensitive patient data The following code snippets comes from the HAPI FHIR documentation.

Cross-Origin Resource Sharing (CORS)

The HAPI FHIR server framework includes an interceptor that can be used to provide CORS functionality on your server. This mechanism relies purely on Java configuration. HAPI’s interceptor is a thin wrapper around Spring Framework’s CorsProcessor class, so it requires Spring to be present on your classpath.

package com.example.fhirexample;

import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import org.springframework.web.cors.CorsConfiguration;

@WebServlet(urlPatterns = {"/fhir/*"}, displayName = "FHIR Server")
public class RestfulServerWithCors extends RestfulServer {
    @Override
    protected  void  initialize() throws  ServletException {
    // ... define your resource providers here ...
    // Define your CORS configuration. This is an example
    // showing a typical setup. You should customize this
    // to your specific needs
    CorsConfiguration  config = new  CorsConfiguration();
    config.addAllowedHeader("x-fhir-starter");
    config.addAllowedHeader("Origin");
    config.addAllowedHeader("Accept");
    config.addAllowedHeader("X-Requested-With");
    config.addAllowedHeader("Content-Type");
    config.addAllowedOrigin("*");
    config.addExposedHeader("Location");
    config.addExposedHeader("Content-Location");
    config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
    // Create the interceptor and register it
    CorsInterceptor  interceptor = new  CorsInterceptor(config);
    registerInterceptor(interceptor);
    }
}

AuthorizationInterceptor

HAPI FHIR provides you with an AuthorizationInterceptor which can be helpful to determine whether a user has the appropriate permission to perform a given task on a FHIR server. This is done by declaring a set of rules that can selectively allow (whitelist) and/or selectively block (blacklist) requests. The interceptor works by allowing you to declare permission based on an individual request coming in.

The AuthorizationInterceptor is used by subclassing it and then registering your subclass with the RestfulServer. The following example shows a subclassed interceptor implementing some basic rules:

package com.example.fhirexample;

import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
import org.hl7.fhir.r4.model.IdType;

import java.util.List;

public class PatientAndAdminAuthorizationInterceptor extends AuthorizationInterceptor {

    @Override
    public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {

        // Process authorization header - The following is a fake
        // implementation. Obviously we'd want something more real
        // for a production scenario.
        //
        // In this basic example we have two hardcoded bearer tokens,
        // one which is for a user that has access to one patient, and
        // another that has full access.
        IdType userIdPatientId = null;
        String authHeader = theRequestDetails.getHeader("Authorization");
        if (isNormalUser(authHeader)) {
            // This user has only access to the Patient resource with id 1.
            // If the user is a specific patient, we create the following rule chain:
            // Allow the user to read anything in their own patient compartment
            // Allow the user to write anything in their own patient compartment
            // If a client request doesn't pass either of the above, deny it
            userIdPatientId = new IdType("Patient", 1L);
            return new RuleBuilder()
                    .allow().read().allResources().inCompartment("Patient", userIdPatientId).andThen()
                    .allow().write().allResources().inCompartment("Patient", userIdPatientId).andThen()
                    .denyAll()
                    .build();
        } else if (isAdmin(authHeader)) {
            // If the user is an admin, allow everything
            return new RuleBuilder()
                    .allowAll()
                    .build();
        } else {
            // Throw an HTTP 401
            throw new AuthenticationException("Missing or invalid Authorization header value");
        }
    }

    private boolean isNormalUser(String authHeader){
        return "Bearer dfw98h38r".equals(authHeader);

    }
    private boolean isAdmin(String authHeader){
        return "Bearer 39ff939jgg".equals(authHeader);
    }

}

After the server was set up and ready to use,I’ve set up a small Angular front-end application where it was possible to retrieve all the Patients and add a Patient. Finally Observations were added which could be linked to a specific Patient. In order not to define all the Javascript interfaces myself I used the following module @ahryman40k/ts-fhir-types.

Conclusion

For several years, the FHIR specification has been providing enhancements at various levels from application development to inter-application integration

We can conclude that setting up a FHIR server in Java is made easy by using the HAPI libraries. They provide different server types for different needs. There are servers that work out-of-the box but there are also possibilities to link existing databases to a HAPI FHIR façade. In addition, the security libraries provided by HAPI make the development process a lot easier and secure.

Youri is an ambitious back-end developer with a problem-solving mindset. He is a team-oriented person who is always motivated to learn and master new skills.