Skip to content

FHIR server notes

Bill Majurski edited this page Jun 5, 2017 · 11 revisions

Tooklit/HAPI integration

This FHIR server is based on the HAPI open source server. These notes discuss how it was modified to link in toolkit and more specifically toolkit simulators.

HAPI/Toolkit integration - a high level view.

HAPI is a FHIR server. Toolkit is a collection of servers. We call those individual servers simulators. They have their own context maintained in the external cache in simdb. Here we are trying to convert HAPI to serve the same high level structure where the endpoint (URL) has within it coded the simulator identity. So first a little HAPI.

We start with ExampleRestfulServlet which is, in the HAPI distribution, an example of their RestfulServer which itself extends HttpServlet. (ExampleRestfulServlet extends RestfulServer extends HttpServlet). We have to modify both ExampleRestfulServlet and the base RestfulServer to manage our simulator mappings.

Managing simulator identities

The first issue to overcome is managing the SimId, the identifier for a specific simulator. There is no place in the RestfulServer to hide it. So we introduce a hack. It is the class

class SimTracker
    static Map<Long, SimContext> contextMap = new HashMap<Long, SimContext>();
    static public void setContext(SimContext cxt) {
        Long threadId = Thread.currentThread().getId();
        contextMap.put(threadId, cxt);
    }
    static public SimContext getContext() {
        Long threadId = Thread.currentThread().getId();
        return contextMap.get(threadId);
    }

It relies on the fact that each request is managed on its own thread. A thread has a thread ID which is used as an index above. This is not so different from depending on a Request Context within the servlet standard.

Nearly the first thing that happens at the start of an incoming request is that

ExampleRestfulServlet:initializeRequestHandler(String requestFullPath)

is called. Here we capture the SimId off the URI and stash it using SimTracker:setContext(SimContext cxt). SimContext contains the SimId. Later when we need it we use SimTracker:getContext() to retrieve the SimId. Obviously this could be used to manage more context than just SimId.

HAPI management of FHIR Resources

Next we must acknowledge that HAPI has a lot of magic going on related to managing Resource. The short story is that for each resource type managed by the server there is a class which looks like this:

class PatientResourceProvider implements IResourceProvider

this class is linked to a resource type by the overridden method

   	@Override
public Class<Patient> getResourceType() {
	return Patient.class;
}

At system startup time HAPI scans known class that implement IResourceProvider, builds an instance and calls this method. It maintains a table linking the name of the resource (actually the class) to the Provider. The Provider contains all the actions you ask for on a resource.

So the

where SimContext is a simple container for the simId. Later in when implementing an operation in HAPI (READ for example)

   	@Read(version = true)
public Patient readPatient(@IdParam IdDt theId) {

we use

   	SimContext simContext = SimTracker.getContext();
SimId simId = simContext.getSimId();

to get back the simId (contained in SimContext). The other request types are

@Create()
@Search()
@Read(version = true)
@Update()

There are a lot of others. They are defined in ca.uhn.fhir.rest.annotation.

What services does HAPI provide us?

HAPI is a full FHIR server (as best I can tell) and actually does too much for us. It is an implementation and we need to build a test tool. The needs are different. We started with ExampleRestfulServlet because it bypasses a lot of the automatic things HAPI could do for us. That’s the good news. The bad news is there are now some things we have to do on our own. What? Basically the entire back end. But that’s were we want to plug in toolkit so that’s ok.

Resource Db

ResDb is the FHIR equivalent of SimDb for our SOAP based simulators. It is separate because it has different requirements. It has enough in common that the class ResDb extends SimDb so the resource DB is actually an extension of the sim (SOAP) DB.

FHIR simulators (or resource sims - haven’t settle on a name yet) have the same top level naming structure. A sim is named

testsession__name

note that like simdb, that is a double underscore a separator. Name must be unique within the test session. It will likely be enforced that a SOAP sim and a FHIR sim cannot have the same name just for better understanding but there is not yet a technical reason for that restriction.

The top level structure of the db similar to simdb:

fhir/
sim_type.txt
simid.txt
simindex/

where

fhir directory holds the main contents of the simulator<br /> sim_type.txt is the simulator type<br /> simid.txt is the simulator id<br /> simindex is new. It is the storage area for the search engine Lucene which we use to index and search FHIR resources. Our code does not manage the contents of this directory, Lucene does. More on this later.

Inside the fhir directory, in a fashion similar to SOAP simulators, there is a directory per operation. One obvious example is PUT. This directory holds all the submitted content.

Each directory under PUT is a toolkit event:

2017_03_22_11_57_43_324/
    date.ser
    Patient1.json

which is timestamp name of a submission event. Inside it is the date of submission (really the same information that is on the directory but in an easy format for code to pull) and the stored resource in JSON format. The file name is the resource type followed by the index which is assigned locally when it is stored.

Handling retrieves

A retrieve (or more specifically a GET) uses the Lucene index for the simulator even if the ID of the resource is provided. This is because we do not store resources by id we store them by submission event. BTW, the index shown above (the 1 on Patient1.json) is not the id of the resource. It is just a descriminator so we can store a bunch of Patient resources in the directory. It has no other significance. The resource contents and the Lucene index hold the real ID.

We now look to the HAPI Provider interface, more specifically to

@Read(version = true)
public Patient readPatient(@IdParam IdDt theId) {

A Provider implementation has a @Read method with one parameter, the ID. We use Lucene to lookup the resource. First we need the Patient ID

String patientId = theId.getIdPart();  // in the FHIR sense

and through our hack the simid

SimContext simContext = SimTracker.getContext();
SimId simId = simContext.getSimId();

and the Lucene lookup

SimIndexer simIndexer = new SimIndexer(simId).open();
List<String> paths = simIndexer.lookupByTypeAndId("Patient", patientId);

Lucene always returns a list of paths since it is usually used with large data sets and queries returning many results. We pull the file out of the ResDb with

resourceString = Io.stringFromFile(new File(paths.get(0)));

and return it through HAPI invocation

return ourCtx.newJsonParser().parseResource(Patient.class, resourceString);

To look at what this means in terms of Lucene we have to open up the code where we indexed the resource.

Indexing resources

To be able to search for a Resource we must index it. There are a number of per-resource-type files that must be created to create and use an index.

gov.nist.toolkit.fhirServer.XResourceProvider - this is the main class managing Resource X<br /> gov.nist.toolkit.fhir.resourceIndexer.X - this is the index builder for Resource X<br />

A good place to start is the class gov.nist.toolkit.fhir.resourceIndexer.Base which is the base class for resource indexers. It’s build method looks like:

ResourceIndex build(def json, SimResource simResource) {
    ResourceIndex ri = new ResourceIndex()
    String resourceType = json.resourceType
    ri.add(new ResourceIndexItem('type', resourceType))
    String id = json.id
    ri.add(new ResourceIndexItem('id', id))
    return ri
}

The ResourceIndex has two items added to it, type and id. These are the two most basic indexes and are necessary for basic lookup. Notice they look a lot like properties. ResourceIndex is the primary data structure for interfacing with Lucene. It contains

List<ResourceIndexItem> items = []
String path

where items is the list of properties we are defining above when we describe Resources and path which is the location in the ResDb where the Resource is stored. New ResourceIndex objects get written to and read from the Lucene database via the class ResDbIndexer. This database is stored in the ResDb under the directory simindex/ that was mentioned above.

Clone this wiki locally