Skip to main contentIBM® FHIR® Server

IBM FHIR Server User's Guide

1 Overview

The IBM FHIR Server implements the HL7 FHIR HTTP API and supports the full set of FHIR-defined resource types. This FHIR server is intended to be a common component for providing FHIR capabilities within health services and solutions.

2 Installation

2.1 Installing a new server

  1. Prereqs: The IBM FHIR Server requires Java 8 or higher and has been tested with OpenJDK 8, OpenJDK 11, and the IBM SDK, Java Technology Edition, Version 8. To install Java on your system, we recommend downloading and installing OpenJDK 8 from https://adoptopenjdk.net/.

  2. To install the IBM FHIR Server, build or download the fhir-install zip installer (e.g. fhir-server-distribution.zip or fhir-install-4.0.0-rc1-20191014-1610). The Maven build creates the zip package under fhir-install/target. Alternatively, releases will be made available from the Releases tab.

  3. Unzip the .zip package into a clean directory (referred to as fhir-installer here):

    mkdir fhir-installer
    cd fhir-installer
    unzip fhir-server-distribution.zip
  4. Determine an install location for the OpenLiberty server and the IBM FHIR Server webapp. Example: /opt/ibm/fhir-server

  5. Run the install.sh/.bat script to install the server:

    ./fhir-server-dist/install.sh /opt/ibm/fhir-server

    This step installs the OpenLiberty runtime and the IBM FHIR Server web application. The Liberty runtime is installed in a directory called wlp within the installation directory that you specify. For example, in the preceding command, the root directory of the Liberty server runtime would be /opt/ibm/fhir-server/wlp.

  6. Configure the fhir-server’s server.xml file as needed by completing the following steps:

    • Configure the ports that the server listen on. The server is installed with only port 9443 (HTTPS) enabled by default. To change the port numbers, modify the values in the httpEndpoint element.
    • Configure a server keystore and truststore. The IBM FHIR Server is installed with a default keystore file that contains a single self-signed certificate for localhost. For production use, you must create and configure your own keystore and truststore files for the FHIR server deployment (that is, generate your own server certificate or obtain a trusted certificate, and then share the public key certificate with API consumers so that they can insert it into their client-side truststore). The keystore and truststore files are used along with the server’s HTTPS endpoint and the FHIR server’s client-certificate-based authentication protocol to secure the FHIR server’s endpoint. For more information, see Section 5.2 Keystores, truststores, and the FHIR server.
    • Configure an appropriate user registry. The FHIR server is installed with a basic user registry that contains a single user named fhiruser. For production use, it’s best to configure your own user registry. For more information about configuring user registries, see the OpenLiberty documentation.

    To override the default fhiruser’s password, one may set an Environment variable FHIR_USER_PASSWORD and for the fhiradmin’s password one may set an Environment variable FHIR_ADMIN_PASSWORD.

  7. Make sure that your selected database product is running and ready to accept requests as needed:

    • By default, the FHIR server is installed with the JDBC persistence layer configured to use an Embedded Derby database. When using the ibmcom/ibm-fhir-server docker image, set the BOOTSTRAP_DB environment variable to true in order to bootstrap this database. For any other configuration, note the database host and port and, if necessary, create a user with privileges for deploying the schema.
  8. Create and deploy the IBM FHIR Server database schema as needed:

    • By default, the FHIR server is installed with the JDBC persistence layer configured to use an Embedded Derby database. When using the ibmcom/ibm-fhir-server docker image, set the BOOTSTRAP_DB environment variable to true in order to bootstrap this database. For any other configuration, use the fhir-persistence-schema module to create and deploy the database schema.
    • The Db2 persistence layer is always tenant-aware, so you must provision a tenant in addition to deploying the schema. Note the tenant name you provisioned and the tenantKey generated by fhir-persistence-schema for the next step.
  9. Configure the fhir-server-config.json1 configuration file as needed:

  10. To start and stop the server, use the Liberty server command:

<WLP_HOME>/bin/server start fhir-server
<WLP_HOME>/bin/server stop fhir-server
  1. After you start the server, you can verify that it’s running properly by invoking the $healthcheck endpoint like this:
curl -k -u '<username>:<password>' 'https://<host>:<port>/fhir-server/api/v4/$healthcheck'

where <username> is one of the users configured in server.xml (default is fhiruser).
Use single quotes around the URL to prevent $healthcheck from being evaluated as an environment variable on unix-based operating systems.

One should see an empty body in the response with a HTTP Response Code 200.

For more information about the capabilities of the implementation, see Conformance.

2.2 Upgrading an existing server

The IBM FHIR Server does not include an upgrade installer. To upgrade a server to the next version, install the new version to a separate location and copy the configuration files from your existing installation (reconciling any configuration-related changes from the new release in the process).

To manage database updates over time, the IBM FHIR Server uses custom tools from the fhir-database-utils project. Through the use of a metadata table, the database utilities can detect the currently installed version of the database schema and apply any new changes that are needed to bring the database to the current level.

Complete the following steps to upgrade the server:

  1. Run the fhir-installer on a separate server.
  2. Configure the new server as appropriate (fhir-server/server.xml and anything under the fhir-server/configDropins, fhir-server/config, and fhir-server/userlib directories).
  3. Back up your database.
  4. Run the migration program (see Section 3.3.1.1 Supported databases).
  5. Disable traffic to the old server and enable traffic to the new server.

2.3 Docker

The IBM FHIR Server includes a Docker image ibmcom/ibm-fhir-server.

Note, logging for the IBM FHIR Server docker image is to stderr and stdout, and is picked up by Logging agents.

The IBM FHIR Server is configured using Environment variables using:

Environment VariableDescription
DISABLED_OPERATIONSA comma-separated list of operations which are disabled on the IBM FHIR Server, for example, validate,import. Note, do not include the dollar sign $

3 Configuration

This chapter contains information about the various ways in which the IBM FHIR Server can be configured by users.

3.1 Liberty server configuration

The IBM FHIR Server is built on the open source OpenLiberty project but also works on the commercial IBM WebSphere Liberty. In documentation and elsewhere, we use Liberty to refer to either/or.

As a Liberty application, the IBM FHIR Server is configurable like any other Liberty server. See https://openliberty.io/docs/latest/reference/config/server-configuration-overview.html for more information.

By default, we include:

  • server.xml with core server details like the Liberty features, HTTP properties, application info, and a default user registry
  • jvm.options with TLS and heap size settings
  • server.env with timezone set to UTC
  • configDropins with server.xml dropins sorted into disabled, defaults, and overrides

3.1.1 Encoded passwords

In the examples within the following sections, you’ll see the default password change-password. In order to secure your server, these values should be changed.

For example, the basic user registry in the default server.xml defines users fhiruser and fhiradmin and variables FHIR_USER_PASSWORD and FHIR_ADMIN_PASSWORD, each with a default value of change-password.

These variables can be changed by setting environment variables by the same name. Alternatively, users can change the server.xml or use Liberty configDropins to change the values.

Optionally, server.xml values can be encoded via the Liberty securityUtility command. For example, to encode a string value with the default {xor} encoding, run the following command:

<WLP_HOME>/bin/securityUtility encode stringToEncode

The output of this command can then be copied and pasted into your server.xml or fhir-server-config.json file as needed. The fhir-server-config.json does not support the securityUtility’s {aes} encoding at this time, but per the limits to protection through password encryptiona, this encoding does not provide significant additional security beyond exclusive or (XOR) encoding.

3.1.2 Logging and trace

The default server.xml includes variables for configuring the log output:

<variable name="TRACE_SPEC" defaultValue="*=info"/>
<variable name="TRACE_FILE" defaultValue="trace.log"/>
<variable name="TRACE_FORMAT" defaultValue="BASIC"/>

By default, INFO level messages will go to both the console and a log file at wlp/usr/servers/fhir-server/logs/messages.log. For the ibmcom/ibm-fhir-server docker image, the default is changed to go just to the console.

To enable tracing, set the TRACE_SPEC variable (e.g. by setting an environment variable by the same name) to a :-delimited list of component = level pair values. For example, to turn on tracing for the SQL generated by fhir-persistence-jdbc, set the TRACE_SPEC to com.ibm.fhir.persistence.jdbc.dao.impl.*=fine:com.ibm.fhir.database.utils.query.QueryUtil=fine.

By default, that trace output appears alongside the messages.log file in a file named trace.log, but the trace output can be sent to the console instead by setting the TRACE_FILE variable to stdout.

For more information on logging and tracing in Liberty, see https://openliberty.io/docs/latest/log-trace-configuration.html.

3.2 FHIR server configuration

3.2.1 Property names

Configuration properties stored within a fhir-server-config.json file are structured in a hierarchical manner. Here is an example:

{
"fhirServer":{
"core":{
"defaultPrettyPrint":false
}
}
}

Throughout this document, we use a path notation to refer to property names. For example, the name of the defaultPrettyPrint property in the preceding example would be fhirServer/core/defaultPrettyPrint.

3.2.2 Tenant-specific configuration properties

The IBM FHIR server supports certain multi-tenant features. One such feature is the ability to set certain configuration properties on a per-tenant basis.

In general, the configuration properties for a particular tenant are stored in the <WLP_HOME>/usr/servers/fhir-server/config/<tenant-id>/fhir-server-config.json file, where <tenant-id> refers to the tenant’s “short name” or tenant id.

The global configuration is considered to be associated with a tenant named default, so those properties are stored in the <WLP_HOME>/usr/servers/fhir-server/config/default/fhir-server-config.json file.

Similarly, tenant-specific search parameters are found at <WLP_HOME>/usr/servers/fhir-server/config/<tenant-id>/extension-search-parameters.json, whereas the global/default extension search parameters are at <WLP_HOME>/usr/servers/fhir-server/config/default/extension-search-parameters.json.

Search parameters are handled like a single configuration properly; providing a tenant-specific file will override the global/default extension search parameters as defined at FHIRSearchConfiguration.

More information about multi-tenant support can be found in Section 4.9 Multi-tenancy.

3.2.3 Compartment Search Performance

The IBM FHIR Server supports the ability to compute and store compartment membership values during ingestion. Once stored, these values can help accelerate compartment-related search queries. To use this feature, update the IBM FHIR Server to at least version 4.5.1 and run a reindex operation as described in the fhir-bucket project README. The reindex operation reprocesses the resources stored in the database, computing and storing the new compartment reference values. After the reindex operation has completed, add the useStoredCompartmentParam configuration element to the relevant tenant fhir-server-config.json file to allow the search queries to use the pre-computed values:

{
"fhirServer": {
"search": {
"useStoredCompartmentParam": true
}
}
}

Note that this parameter only enables or disables the compartment search query optimization feature. The compartment membership values are always computed and stored during ingestion or reindexing, regardless of the setting of this value. After the reindex operation is complete, it is recommended to set useStoredCompartmentParam to true. No reindex is required if this value is subsequently set to false.

3.3 Persistence layer configuration

The IBM FHIR Server allows deployers to select a persistence layer implementation that fits their needs. Currently, the server includes a JDBC persistence layer which supports Apache Derby, IBM Db2, and PostgreSQL. However, Apache Derby is not recommended for production usage.

Before you can configure the server to use the JDBC persistence layer implementation, you first need to prepare the database. This step depends on the database product in use and is covered in more detail in Section 3.3.1.1 Supported databases.

The IBM FHIR Server is delivered with a default configuration that is already configured to use the JDBC persistence layer implementation with an Embedded Derby database. This provides the easiest out-of-the-box experience since it requires very little setup.

The IBM FHIR Server persistence configuration is split between fhir-server-config.json and the Liberty server.xml and configDropins.

Within fhir-server-config.json, the value of the fhirServer/persistence/factoryClassname is used to instantiate a FHIRPersistence object. By default, the server is configured to use the FHIRPersistenceJDBCFactory:

{
"fhirServer": {
"persistence": {
"factoryClassname": "com.ibm.fhir.persistence.jdbc.FHIRPersistenceJDBCFactory",
}
}

3.3.1 The JDBC persistence layer

When the FHIRPersistenceJDBCFactory is in use, the fhirServer/persistence/datasources property must specify a mapping from datastore-id values to Liberty datasource definitions. For example, here is the configuration for a datastore with id default that is configured for the jdbc/fhir_default_default datasource of type postgresql:

{
"fhirServer":{
"persistence":{
"factoryClassname":"com.ibm.fhir.persistence.jdbc.FHIRPersistenceJDBCFactory",
"datasources": {
"default": {
"type": "postgresql",
"currentSchema": "fhirdata",
"jndiName": "jdbc/fhir_default_default"

Both the type and the currentSchema properties are required. The type value must be set to one of the supported JDBC types (currently db2, postgresql, or derby) and it must match the actual database type referenced by the datasource definition. If the type does not match, the behavior is undefined. Because the IBM FHIR Server does not quote the schema name in our generated queries, the currentSchema property is effectively case-insensitive.

The jndiName property is optional; it will default to jdbc/fhir_<tenantId>_<dsId> where <tenantId> and <dsId> represent the tenant and datastore ids respectively4.

The datasource definitions themselves are configured in accordance with the Liberty documentation on Relational database connections with JDBC. Previous versions of the IBM FHIR Server supported a proxy datasource that allowed for datasource definitions in the fhir-server-config.json, but now Liberty JDBC datasource configuration is used due to benefits related to configuring pool sizes, transaction recovery, and monitoring.

For example, the fhir-server-config snippet from above would have a corresponding Liberty config like this:

<server>
<!-- ============================================================== -->
<!-- TENANT: default; DSID: default; TYPE: read-write -->
<!-- ============================================================== -->
<dataSource id="fhirDefaultDefault" jndiName="jdbc/fhir_default_default" type="javax.sql.XADataSource" statementCacheSize="200" syncQueryTimeoutWithTransactionTimeout="true" validationTimeout="30s">
<jdbcDriver javax.sql.XADataSource="org.postgresql.xa.PGXADataSource" libraryRef="sharedLibPostgres"/>
<properties.postgresql
serverName="postgres_postgres_1"
portNumber="5432"

Datasource elements should not be defined in the main server.xml file but instead should be defined in the configDropins/overrides directory. See the Liberty Profile Server configuration guide for more details and general guidance on creating modular configurations.

The IBM FHIR Server is packaged with the following sample datasource definitions at configDropins/disabled:

  • datasource-postgresql.xml
  • datasource-db2.xml
  • datasource-derby.xml

There are 3 libraries defined in the main server.xml that reference the required client drivers for each database type:

  • sharedLibDerby
  • sharedLibDb2
  • sharedLibPostgres

When a datasource definition is included in a configDropin under configDropins/overrides, this file is picked up when the server starts as indicated by the following AUDIT message:

[AUDIT ] CWWKG0093A: Processing configuration drop-ins resource: <WLP_HOME>/usr/servers/fhir-server/configDropins/overrides/datasource.xml

The IBM FHIR Server will look up the tenant and datastore id for each request and use the corresponding JNDI name to obtain a connection for the corresponding datasource.

3.3.1.1 Supported databases

Embedded Derby (default)

If you are using the ibmcom/ibm-fhir-server docker image, you can ask the entrypoint script to create (bootstrap) the database and the schema during startup by setting the BOOTSTRAP_DB environment variable to true.

This database bootstrap step is only supported for Embedded Derby and will only bootstrap the default datastore of the default tenant (the default for requests with no tenant or datastore headers).

Reminder: the Embedded Derby support is designed to support simple getting started scenarios and is not recommended for production use.

<server>
<!-- ============================================================== -->
<!-- This datasource aligns with the Apache Derby database that is -->
<!-- created by the IBM FHIR Server's DB_BOOTSTRAP process. -->
<!-- ============================================================== -->
<dataSource id="fhirDefaultDefault" jndiName="jdbc/fhir_default_default" type="javax.sql.XADataSource" statementCacheSize="200" syncQueryTimeoutWithTransactionTimeout="true" validationTimeout="30s">
<jdbcDriver javax.sql.XADataSource="org.apache.derby.jdbc.EmbeddedXADataSource" libraryRef="sharedLibDerby"/>
<properties.derby.embedded createDatabase="create" databaseName="derby/fhirDB"/>
<connectionManager maxPoolSize="50" minPoolSize="10"/>
Db2

If you configure the FHIR server to use an IBM Db2 database, you must:

  1. Create the database if it doesn’t already exist.

  2. Execute the fhir-persistence-schema utility to create the necessary schemas (tables, indices, stored procedures, etc) and tenants.

  3. Configure the IBM FHIR Server with the tenantKey generated in step number 2.

An executable fhir-persistence-schema jar can be downloaded from the project’s Releases tab and documentation can be found at https://github.com/IBM/FHIR/tree/main/fhir-persistence-schema.

For a detailed guide on configuring IBM Db2 on Cloud for the IBM FHIR Server, see DB2OnCloudSetup.

Since release 4.3.2 you can use the search.reopt query optimizer hint to improve the performance of certain search queries involving multiple search parameters. This optimization is currently only available for Db2. Valid values are “ALWAYS” and “ONCE”. See Db2 documentation for REOPT for more details.

PostgreSQL

If you configure the FHIR server to use a PostgreSQL database, you must:

  1. create the database if it doesn’t already exist

  2. execute the fhir-persistence-schema utility with a db-type of postgresql to create the necessary schemas (tables, indices, functions, etc)

An executable fhir-persistence-schema jar can be downloaded from the project’s Releases tab and documentation can be found at https://github.com/IBM/FHIR/tree/main/fhir-persistence-schema.

Since release 4.5.5 you can set the searchOptimizerOptions/from_collapse_limit and searchOptimizerOptions/join_collapse_limit properties to improve the performance of certain search queries involving multiple search parameters. This optimization is currently only available for PostgreSQL.

Other

To enable the IBM FHIR Server to work with other relational database systems, see https://ibm.github.io/FHIR/guides/BringYourOwnPersistence#adding-support-for-another-relational-database

3.3.1.2 Datastore configuration examples

To understand how the configuration properties are defined for one or more datastores, let’s start off with a couple of examples.

Example 1

Here is a simple example of a single (default) Derby datastore.

config/default/fhir-server-config.json

{
"fhirServer":{
"persistence":{
"factoryClassname":"com.ibm.fhir.persistence.jdbc.FHIRPersistenceJDBCFactory",
"datasources": {
"default": {
"type": "derby",
"currentSchema": "APP"

configDropins/overrides/datasource-derby.xml

<server>
<library id="fhirSharedLib">
<fileset dir="${shared.resource.dir}/lib/derby" includes="*.jar"/>
</library>
<!-- ============================================================== -->
<!-- TENANT: default; DSID: default; TYPE: read-write -->
<!-- ============================================================== -->
<dataSource id="fhirDefaultDefault" jndiName="jdbc/fhir_default_default" type="javax.sql.XADataSource" statementCacheSize="200" syncQueryTimeoutWithTransactionTimeout="true" validationTimeout="30s">

In this example, we define an embedded Derby database named derby/fhirDB (a location relative to the <WLP_HOME>/usr/servers/fhir-server directory). The datastore-id associated with this datastore is default, which is the value that is used if no X-FHIR-DSID request header is found in the incoming request. So, when only a single database is being used, it’s wise to leverage the default datastore-id value to allow REST API consumers to avoid having to set the X-FHIR-DSID request header on each request.

Example 2

This example shows a slightly more complex scenario. In this scenario, the acme tenant would like to store data in one of two study-specific Db2 databases with datastore-id values study1 and study2.

Furthermore, the REST API consumers associated with Acme applications will be coded to always set the X-FHIR-TENANT-ID request header to be acme and the X-FHIR-DSID request header to the specific datastore-id associated with each request (either study1 or study2). In this case, the following properties would be configured:

config/acme/fhir-server-config.json

{
"__comment":"Acme's FHIR server configuration",
"fhirServer":{
"persistence":{
"factoryClassname":"com.ibm.fhir.persistence.jdbc.FHIRPersistenceJDBCFactory",
"datasources": {
"study1": {
"tenantKey": "<the-tenant-key>",

configDropins/overrides/datasources-acme.xml

<server>
<library id="fhirSharedLib">
<fileset dir="${shared.resource.dir}/lib/db2" includes="*.jar"/>
</library>
<!-- ============================================================== -->
<!-- TENANT: acme; DSID: study1; TYPE: read-write -->
<!-- ============================================================== -->
<dataSource id="fhirDefaultDefault" jndiName="jdbc/fhir_acme_study1" type="javax.sql.XADataSource" statementCacheSize="200" syncQueryTimeoutWithTransactionTimeout="true" validationTimeout="30s">

Note that because this example is using the default FHIR server naming scheme for datasource JNDI names, there is no need to include the ‘jndiName’ property in the fhir-server-config.json, although you can specify it should you wish to make the mapping clear.

Additionally, note that each datasource definition in the configDropin gets its own connection pool with properties defined by the ‘connectionManager’ element.

3.3.1.3 Database Access TransactionManager Timeout

The TransactionManager controls the timeout of database queries.

To modify the default transaction timeout value, set the environment variable FHIR_TRANSACTION_MANAGER_TIMEOUT or enter the value in the server.env file at the root of the WLP fhir-server instance. Example values are 120s (seconds) or 2m (minutes).

3.4 “Update/Create” feature

Normally, the update operation is invoked with a FHIR resource which represents a new version of an existing resource. The resource specified in the update operation would contain the same id of that existing resource. If a resource containing a non-existent id were specified in the update invocation, an error would result.

The FHIR specification defines optional behavior for the update operation where it can create a new resource if a non-existent resource is specified in the invocation. The FHIR server supports this optional behavior via the fhirServer/persistence/common/updateCreateEnabled configuration parameter. If this configuration parameter is set to true (the default), then the update operation will create a new resource if it is invoked with a resource containing a non-existent id. If the option is set to false, then the optional behavior is disabled and an error would be returned.

The following example shows the JSON for enabling update operations to create resources:

{
"fhirServer":{
"persistence":{
"common":{
"updateCreateEnabled":true
}
}

4 Customization

You can modify the default server implementation by taking advantage of the IBM FHIR server’s extensibility. The following extension points are available:

  • Custom operations framework: The IBM FHIR Server defines an operations framework that builds on the FHIR OperationDefinition resource in order to extend the FHIR REST API with custom endpoints.
  • Pluggable audit service: Logging and auditing options including Cloud Auditing Data Federation (CADF) over Apache Kafka.
  • Persistence interceptors: Intercept requests before and/or after each persistence action.
  • Resource validation: FHIRPath-based validation of FHIR resources on create or update with the ability to extend the system with custom constraints.

4.1 Extended operations

In addition to the standard REST API (create, update, search, and so forth), the IBM FHIR Server supports the FHIR operations framework as described in the FHIR specification.

4.1.1 Packaged operations

The FHIR team provides implementations for the standard $validate, $document, $everything, $expand, $lookup, $subsumes, $closure, $export, $import, $convert, $apply and $translate operations, as well as a custom operation named $healthcheck, which queries the configured persistence layer to report its health.

The server also bundles $reindex to reindex instances of Resources so they are searchable, $retrieve-index to retrieve lists of resources available to be reindexed, and $erase to hard delete instances of Resources. To learn more about the $erase operation, read the design document.

To extend the server with additional operations, see Section 4.1.2 Custom operations

4.1.1.1 $validate

The $validate operation checks whether the attached content would be acceptable either generally, or as a create, update, or delete against an existing resource instance or type. By default, the $validate operation will validate a resource against the base specification and any profiles asserted in its Resource.meta.profile element. The default behavior may be changed in one of the following ways:

  • If a profile is specified via the optional profile parameter, the $validate operation will validate a resource against the base specification and the specified profile only. It will not validate against any other profiles asserted in the Resource.meta.profile element.
  • If the profile parameter is not specified, but the mode parameter is specified, and the mode parameter value is either create or update, the $validate operation will validate a resource against the base specification and any profiles asserted in its Resource.meta.profile element, but will do so based on profile configuration properties specified in the fhirServer/resources/<resourceType>/profiles section of the fhir-server-config.json file (see the Configuration properties reference for configuration details).

4.1.1.2 $document

The $document operation generates a fully bundled document from a composition resource.

4.1.1.3 $healthcheck

The $healthcheck operation returns the health of the FHIR server and its datastore. In the default JDBC persistence layer, this operation creates a connection to the configured database and return its status. The operations returns 200 OK when healthy. Otherwise, it returns an HTTP error code and an OperationOutcome with one or more issues.

4.1.2 Custom operations

In addition to the provided operations, the FHIR server supports user-provided custom operations through a Java Service Provider Interface (SPI).

To contribute an operation:

  1. Implement each operation as a Java class that extends com.ibm.fhir.operation.AbstractOperation from fhir-operation.jar. Ensure that your implementation returns an appropriate OperationDefinition in its getDefinition() method, because the framework validates both the request and response payloads to ensure that they conform to the definition.

  2. Create a file named com.ibm.fhir.operation.FHIROperation with one or more fully qualified FHIROperation classnames and package it in your jar under META-INF/services/.

  3. Include your jar file under the <WLP_HOME>/usr/servers/fhir-server/userlib/ directory of your installation.

  4. Restart the FHIR server. Changes to custom operations require a server restart, because the server discovers and instantiates operations during server startup only.

After you register your operation with the server, it is available via HTTP POST at [base]/api/1/$<yourCode>, where <yourCode> is the value of your OperationDefinition’s code.

4.2 Notification Service

The FHIR server provides a notification service that publishes notifications about persistence events, specifically create, update, and delete operations. The notification service can be used by other Healthcare components to trigger specific actions that need to occur as resources are being updated in the FHIR server datastore.

The notification service supports two implementations: WebSocket and Kafka.

4.2.1 FHIRNotificationEvent

The FHIRNotificationEvent class defines the information that is published by the notification service. Each notification event published to the WebSocket or Kafka topic is an instance of FHIRNotificationEvent, serialized as a JSON object. This JSON object will have the following fields:

Field nameTypeDescription
operationTypeStringThe operation associated with the notification event. Valid values are create and update.
locationStringThe location URI of the resource associated with the notification event. To retrieve this resource, invoke a GET request using the location URI value as the URL string.
lastUpdatedStringThe date and time of the last update made to the resource associated with the notification event.
resourceIdStringThe logical id of the resource associated with the notification event.
resourceStringA stringified JSON object which is the resource associated with the notification event.
tenantIdStringThe tenant that generated this notification
datasourceIdStringThe datasource used by the tenant

The following JSON is an example of a serialized notification event:

{
"lastUpdated":"2016-06-01T10:36:23.232-05:00",
"location":"Observation/3859/_history/1",
"operationType":"create",
"resourceId":"3859",
"tenantId":"default",
"datasourceId":"default",
"resource":{ …<contents of resource>… }
}

If the resource is over the limit specified in fhirServer/notifications/common/maxNotificationSizeBytes, the default value is to subset id, meta, resourceType, and all Resource required fields and add the subset to the FHIRNotificationEvent. In alternative configurations, user may set fhirServer/notifications/common/maxNotificationSizeBehavior to omit and subsequently retrieve the resource using the location.

4.2.2 WebSocket

The WebSocket implementation of the notification service will publish notification event messages to a WebSocket. To enable WebSocket notifications, set the fhirServer/notifications/websocket/enabled property to true, as in the following example:

{
"fhirServer":{
"notifications":{
"websocket":{
"enabled":true
}

The WebSocket location URI is ws://<host>:<port>/fhir-server/api/v4/notification, where <host> and <port> represent the host and port of the FHIR server’s REST API endpoint. So for example, if the FHIR server endpoint’s base URL is https://localhost:9443/fhir-server/api/v4 then the corresponding location of the WebSocket would be ws://localhost:9443/fhir-server/api/v4/notification.

4.2.3 Kafka

The Kafka implementation of the notification service will publish notification event messages to a Kafka topic. To configure the Kafka notification publisher, configure properties in the fhir-server-config.json file as indicated in the following example:

{
"fhirServer":{
"notifications":{
"kafka":{
"enabled":true,
"topicName":"fhirNotifications",
"connectionProperties":{

The fhirServer/notifications/kafka/enabled property is used to enable or disable the Kafka publisher component.

The fhirServer/notifications/kafka/topicName property is used to configure the appropriate topic name to which the notification events should be published.

The fhirServer/notifications/kafka/connectionProperties property group is used to configure the properties necessary to successfully connect to the Kafka server. You can specify an arbitrary group.id. The bootstrap.servers property is required, but the rest are optional, although if your Kafka server is configured to require an SSL connection and client authentication, then the remaining properties must also be set. For more details about Kafka-related properties, see the Kafka documentation.

In the connectionProperties property group in preceding example, you’ll notice that the password-related properties have encoded values. To store a value requiring security (such as a password), you can use Liberty’s securityUtility command to encode the value. See Section 3.1 Encoded passwords for details.

Before you enable Kafka notifications, it’s important to understand the topology of the environment in which the FHIR server instance will be running. Your topic name selection should be done in consideration of the topology. If you have multiple instances of the FHIR server clustered together to form a single logical endpoint, then each of those instances should be configured to use the same Kafka topic for notifications. This is so that notification consumers (subscribers) can subscribe to a single topic and receive all the notifications published by each of the FHIR server instances within the cluster.

On the other hand, if you have two completely independent FHIR server instances, then you should configure each one with its own topic name.

The FHIRNotificationEvent is asynchronous by default. If you want to specify a synchronous request, you can set fhirServer/notifications/kafka/sync to true, which ensures no message is lost in publishing, however it does add latency in each request.

4.2.3 NATS

The NATS implementation of the notification service publishes notification event messages to a NATS streaming cluster. To configure the NATS notification publisher, configure properties in the fhir-server-config.json file as shown in the following example:

{
"fhirServer":{
...
"notifications":{
...
"nats": {
"enabled": true,
"cluster": "nats-streaming",
"channel": "fhirNotifications",

Set the fhirServer/notifications/nats/enabled property to true and provide the name of your NATS cluster for the value of fhirServer/notifications/nats/cluster. You may leave fhirServer/notifications/nats/channel and fhirServer/notifications/nats/clientId as defined. Provide the URL for one or more NATS servers as the value for fhirServer/notifications/nats/servers.

To use TLS to connect to your NATS cluster, set fhirServer/notifications/nats/useTLS to true and provide client truststore and keystore locations and passwords as the remaining config values. Ensure that your NATS cluster is configured for TLS client connections.

To store a value requiring security, such as a password, use Liberty’s securityUtility command to encode the value. See Section 3.1 Encoded passwords for details.

4.2.4 Resource type filtering

By default, notification messages are published for all create and update persistence operations. However, the FHIR server allows you to configure a list of resource types for which notification events will be published. To do this, list the resource types for which you want to generate notifications in an array of strings within the fhirServer/notifications/common/includeResourceTypes property in the fhir-server-config.json file, as in the following example:

{
"fhirServer":{
"notifications":{
"common":{
"includeResourceTypes":[
"Observation",
"Patient"

With the includeResourceTypesproperty set as in the preceding example, the FHIR server publishes notification events only for Patient and Observation resources. If you omit this property or set its value to [] (an empty array), then the FHIR server publishes notifications for all resource types.

4.3 Persistence interceptors

The IBM FHIR Server supports a persistence interceptor feature that enables users to add their own logic to the REST API processing flow around persistence events. This can be used to enforce application-specific business rules associated with resources. Interceptor methods are called immediately before or after each persistence operation.

4.3.1 FHIRPersistenceInterceptor interface

A persistence interceptor implementation must implement the com.ibm.fhir.persistence.interceptor.FHIRPersistenceInterceptor interface.

Each interceptor method receives a parameter of type FHIRPersistenceEvent, which contains context information related to the request being processed at the time that the interceptor method is invoked. It includes the FHIR resource, security information, request URI information, and the collection of HTTP headers associated with the request.

There are many use cases for persistence interceptors:

  1. Enforce certain application-specific governance rules, such as making sure that a patient has signed a consent form prior to allowing his/her data to be persisted. For example, the beforeCreate and beforeUpdate methods could verify that the patient has a consent agreement on file and, if not, then throw a FHIRPersistenceInterceptorException to prevent the create or update events from completing. The exception thrown by the interceptor method should include one or more OperationOutcome issues and these issues will be added to an OperationOutcome in the REST API response. The HTTP status code of the response will be determined by the IssueType of the first issue in the list.

  2. Perform additional access control. For example, beforeSearch can be used to alter the incoming SearchContext (e.g. by adding additional search parameters). Similarly afterRead, afterVRead, afterHistory, and afterSearch can be used to verify that the end user is authorized to access the resources before they are returned.

It is also possible to modify the incoming resources from the beforeCreate and beforeUpdate methods. For example, an interceptor could be used to add tags to resources on their way into the server. However, it is important to realize that interceptors are called after resource validation. Therefore, interceptor authors must be careful not to alter the resources in a way that breaks conformance with the profiles claimed in Resource.meta.profile or the secondary constraints in the specification. When in doubt, interceptors that modify the incoming resource can use the FHIRValidator to re-validate the resource(s) after they are altered.

4.3.2 Implementing a persistence interceptor

To implement a persistence interceptor, complete the following steps:

  1. Develop a Java class which implements the FHIRPersistenceInterceptor interface.

  2. Store the fully-qualified classname of your interceptor implementation class in a file called :

    META-INF/services/com.ibm.fhir.persistence.interceptor.FHIRPersistenceInterceptor

    Here’s an example of the file contents:

    com.ibm.mysolution.MyInterceptor

  3. Copy your jar to the <WLP_HOME>/usr/servers/fhir-server/userlib directory so that it is accessible to the FHIR server via the classpath (the server.xml file contains a library element that defines this directory as a shared library).

  4. Re-start the FHIR server.

4.4 Registry resources

The IBM FHIR Server includes a dynamic registry of conformance resources. The registry pre-packages all FHIR Definitions from the specification and uses a Java ServiceLoader to discover additional registry resource providers on the classpath.

The server consults this registry for:

  • StructureDefinition, ValueSet, and CodeSystem resources for resource validation.
  • SearchParameter and CompartmentDefinition resources for search.
  • ValueSet and CodeSystem resources for terminology operations.
  • And more.

One common technique for extending FHIR with a set of conformance resources is to build or reference an Implementation Guide.

The IBM FHIR Server includes a PackageRegistryResourceProvider for registering implementation guide resources.

Additionally, we pre-package a number of popular implementation guides and make those available from both our GitHub Releases and Maven Central.

Finally, the IBM FHIR Server includes a built-in ServerRegistryResourceProvider that can be used to bridge conformance resources from the tenant data store (uploaded through the REST API) to the registry. This provider can be enabled/disabled via the fhirServer/core/serverRegistryResourceProviderEnabled property, but we recommend leaving it disabled for performance-intensive workloads.

4.5 Resource validation

As mentioned in the previous section, the IBM FHIR Server registry is consulted during resource validation.

4.5.1 HL7 spec-defined validation support

The FHIR specification provides a number of different validation resources including:

  1. XML Schemas
  2. ISO XML Schematron rules
  3. Structure Definitions / Profiles for standard resource types, data types and built-in value sets

The FHIR server validates incoming resources for create and update interactions using the resource definitions and their corresponding FHIRPath constraints. Additionally, the FHIR server provides the $validate operation that API consumers can use to POST resources and get validation feedback:

POST <base>/<resourceType>/$validate

4.5.2 Extension validation

FHIR resources can be extended. Each extension has a url and servers can use these urls to validate the contents of the extension.

The IBM FHIR Server performs extension validation by looking up the extension definition in its internal registry. If an instance contains extensions that are unknown to the server, then the server will include a validation warning that indicates this extension could not be validated.

4.5.3 Profile validation

The IBM FHIR Server can be extended with custom profile validation. This allows one to apply validation rules on the basis of the Resource.meta.profile values included on the resource instance.

For example, you can configure a set of FHIRPath Constraints to run for resources that claim conformance to the http://ibm.com/fhir/profile/partner profile when you see an input resource like the following:

{
"resourceType" : "Patient",
"meta": {
"profile": [ "http://ibm.com/fhir/profile/partner" ]
},
"name" : [ {
"family" : [ "Doe" ],
"given" : [ "John" ]
} ],

The following configuration parameters can be used to specify rules relating to the set of profiles that are specified in a resource’s meta.profile element:

  • fhirServer/resources/<resourceType>/profiles/atLeastOne - this configuration parameter is used to specify a set of profiles, at least one of which a resource must claim conformance to and be successfully validated against in order to be persisted to the FHIR server.
  • fhirServer/resources/<resourceType>/profiles/notAllowed - this configuration parameter is used to specify a set of profiles to which a resource is not allowed to claim conformance.
  • fhirServer/resources/<resourceType>/profiles/allowUnknown - this configuration parameter is used to indicate whether a warning or an error is issued if a profile specified in a resource’s meta.profile element is not loaded in the FHIR server. The default value is true, meaning unknown profiles are allowed to be specified. The profile will be ignored and just a warning will be returned. If set to false, this means unknown profiles are not allowed to be specified. An error will be returned and resource validation will fail.
  • fhirServer/resources/<resourceType>/profiles/defaultVersions - this configuration parameter is used to specify which version of a profile will be used during resource validation if the profile specified in a resource’s meta.profile element does not contain a version. If a default profile version is not configured using this configuration parameter for an asserted profile, the FHIR server will determine the default version to use for validation.

Before calling the FHIR validator to validate a resource against the set of profiles specified in its meta.profile element that it is claiming conformance to, the following pre-validation will be performed for that set of profiles based on the configuration parameters listed above:

  1. If any specified profile does not contain a version, and that profile is in the set of profiles configured to have a default version via the fhirServer/resources/<resourceType>/profiles/defaultVersions configuration parameter, the default version for that profile will be appended to the profile name, and it is this new profile name containing the version which will be evaluated against in the following steps.
  2. If the fhirServer/resources/<resourceType>/profiles/notAllowed configuration parameter is set to a non-empty list, an error will be returned for any specified profile that is in the list, and validation will fail.
  3. If the fhirServer/resources/<resourceType>/profiles/allowUnknown configuration parameter is set to false, an error will be returned for any specified profile that is not loaded in the FHIR server, and validation will fail.
  4. If the fhirServer/resources/<resourceType>/profiles/atLeastOne configuration parameter is set to a non-empty list, an error will be returned if none of the specified profiles is in the list, and validation will fail.

If the resource passes pre-validation successfully, it will then be passed to the FHIR validator to validate conformance to the specified set of profiles.

The following example configuration shows how to define these configuration parameters:

{
"fhirServer":{
"resources":{
"open": true,
"Observation": {
"profiles": {
"atLeastOne": [
"http://ibm.com/fhir/profile/partnerA",

Given this configuration, in order for an Observation resource to be persisted to the FHIR server:

  • The resource’s meta.profile element must specify either the http://ibm.com/fhir/profile/partnerA profile or the http://ibm.com/fhir/profile/partnerB|1.0 profile or both, based on the fhirServer/resources/Observation/profiles/atLeastOne list. Other profiles may be specified as well assuming they pass the following checks.
  • The resource’s meta.profile element must not specify any profile which is specified in the fhirServer/resources/Resource/profiles/notAllowed list. Since the notAllowed property is not specified in the fhirServer/resources/Observation/profiles section, it is inherited from the fhirServer/resources/Resource/profiles section if specified there.
  • The resource’s meta.profile element must not specify any profile which is not loaded in the FHIR server, based on the fhirServer/resources/Observation/profiles/allowUnknown value of false.
  • The resource must successfully validate against all specified profiles. Note that if the http://ibm.com/fhir/profile/partnerA profile is specified, what is actually evaluated against and eventually passed to the FHIR validator is http://ibm.com/fhir/profile/partnerA|1.1. This is because the http://ibm.com/fhir/profile/partnerA profile is specified in the fhirServer/resources/Observation/profiles/defaultVersions set with a default version of 1.1.

In order for a Patient resource to be persisted to the FHIR server:

  • The resource’s meta.profile element is not required to specify any profile since the atLeastOne property is not specified in the fhirServer/resources/Patient/profiles section, nor is it specified in the fhirServer/resources/Resource/profiles section, where the property would be inherited from if specified. Any valid Patient profile may be specified assuming it passes the following checks.
  • The resource’s meta.profile element cannot specify either the http://ibm.com/fhir/profile/partnerC profile or the http://ibm.com/fhir/profile/partnerD|2.0 profile, based on the fhirServer/resources/Patient/profiles/notAllowed list.
  • The resource’s meta.profile element may specify a profile which is not loaded in the FHIR server. Based on the absence of the allowUnknown property in the fhirServer/resources/Patient/profiles section, as well as the absence of that property in the fhirServer/resources/Resource/profiles section (where the property would be inherited from if specified), the default value of true is used. This means unknown profiles (not loaded in the FHIR server) will be allowed and will simply be ignored.
  • The resource must successfully validate against all specified profiles. Note that since the defaultVersions property is not specified in the fhirServer/resources/Patient/profiles section, this property will be inherited from the fhirServer/resources/Resource/profiles/defaultVersions property. So if a profile is specified in the resource’s meta.profile element that is in the set of defaultVersions profiles, what will actually be evaluated against and eventually passed to the FHIR validator is the original profile name with its specified default version appended to it.

If a profile in either the list specified by the fhirServer/resources/<resourceType>/profiles/atLeastOne configuration parameter or the list specified by the fhirServer/resources/<resourceType>/profiles/notAllowed configuration parameter contains a version, for example http://ibm.com/fhir/profile/partner|1.0, then a profile of the same name specified in the resource’s meta.profile element will only be considered a match if it contains exactly the same version. However, if a profile in the lists specified by the configuration parameters does not contain a version, for example http://ibm.com/fhir/profile/partner, then a profile of the same name specified in the resource’s meta.profile element will be considered a match whether it contains a version or not.

Keep in mind that a profile name specified in the resource’s meta.profile element could be modified due to the resource’s fhirServer/resources/<resourceType>/profiles/defaultVersions configuration. It is this modified profile name that is used in the matching process and in the resource’s validation, but only for those purposes. The meta.profile element of the original resource itself is not updated with the modified profile name.

Using the example configuration above for the Observation resource type, if the profile http://ibm.com/fhir/profile/partnerA|3.2 was specified in a resource’s meta.profile element then it would be considered a match for the http://ibm.com/fhir/profile/partnerA profile specified in the fhirServer/resources/Observation/profiles/atLeastOne list. Conversely, if the profile http://ibm.com/fhir/profile/partnerB was specified in the resource’s meta.profile element then it would not be considered a match for the http://ibm.com/fhir/profile/partnerB|1.0 profile specified in the fhirServer/resources/Observation/profiles/atLeastOne list.

The IBM FHIR Server pre-packages all conformance resources from the core specification.

See Validation Guide - Optional profile support for a list of pre-built Implementation Guide resources and how to load them into the IBM FHIR server.

See Validation Guide - Making profiles available to the fhir registry for information about how to extend the server with additional Implementation Guide artifacts.

In addition to supporting tenant-specific search parameter extensions as described in Section 3.2.2 Tenant-specific configuration properties, the IBM FHIR Server also supports loading search parameters from the registry.

For performance reasons, the registry search parameters are retrieved once and only once during startup.

The set of search parameters can filtered / refined via fhirServer/resources/[resourceType]/searchParameters as described in the Search configuration guide.

4.7 FHIR client API

4.7.1 Overview

In addition to the server, we also offer a Java API for invoking the FHIR REST APIs. The IBM FHIR Client API is based on the JAX-RS 2.0 standard and provides a simple properties-driven client that can be easily configured for a given endpoint, mutual authentication, request/response logging, and more.

4.7.2 Maven coordinates

To use the FHIR Client from your application, specify the fhir-client artifact as a dependency within your pom.xml file, as in the following example:

<dependency>
<groupId>com.ibm.fhir</groupId>
<artifactId>fhir-client</artifactId>
<version>${fhir.client.version}</version>
</dependency>

4.7.3 Sample usage

For examples on how to use the IBM FHIR Client, look for tests like com.ibm.fhir.client.test.mains.FHIRClientSample from the fhir-client project in git. Additionally, the FHIR Client is heavilly used from our integration tests in fhir-server-test.

4.8 Using local references within request bundles

Inter-dependencies between resources are typically defined by one resource containing a field of type Reference which contains an external reference5 to another resource. For example, an Observation resource could reference a Patient resource via the Observation’s subject field. The value that is stored in the Reference.reference field (for example, Observation.subject.reference in the case of the Observation resource) could be an absolute URL, such as https://fhirserver1:9443/fhir-server/api/v4/Patient/12345, or a relative URL, such as Patient/12345.

As described above, in order to establish a reference to a resource, you must first know its resource identifier. However, if you are using a request bundle to create both the referenced resource (Patient in this example) and the resource which references it (Observation), then it is impossible to know the Patientresource identifier before the request bundle has been processed (that is, before the new Patient resource is created).

Thankfully, the HL7 FHIR specification defines a way to express a dependency between two resources within a request bundle by using a local identifier to identify the resource being referenced, and a local reference6 to reference the resource via its local identifier. In the following example, a request bundle contains a POST request to create a new Patient resource, along with a POST request to create a new Observation resource that references that Patient:

{
"resourceType": "Bundle",
"type": "batch",
"entry" : [ {
"fullUrl" : "urn:uuid:7113a0bb-d9e0-49df-9855-887409388c69",
"resource" : {
"resourceType" : "Patient",
},

To define a local identifier for a resource, you must specify it in the Bundle.entry.fullUrl field of the resource’s bundle entry. The HL7 FHIR specification recommends that the local identifier be the persistent identity of the resource if known, otherwise a UUID value prefixed with urn:uuid: as in the preceding example. However, the FHIR server does not require the use of a UUID value. You can use any fullUrl value you like as long as it is unique within the bundle. For example, you can use urn:Patient_1, urn:ABC, or urn:Foo.

After you define a local identifier for the referenced resource, you can then define one or more references to that resource by using the local identifier instead of an external identifier. In the preceding example, you can see that the Observation’s subject.reference field specifies the Patient’s local identifier as specified in the fullUrl field of the Patient’s request entry.

4.8.1 Processing rules

The following processing rules apply for the use of local references within a request bundle:

  1. A local identifier must be defined via a request bundle entry’s fullUrl field in order for that local identifier to be used in a local reference.

  2. Local references will only be recognized for local identifiers associated with request bundle entries with a request method of POST or PUT.

  3. POST requests will be processed before PUT requests.

  4. There is no order dependency within a request bundle for bundle entries defining local identifiers and bundle entries which reference those local identifiers via local references.
  5. If a resource in a request bundle entry contains a field of type `Reference` having a value which is a relative URL, and if that bundle entry has a local identifier (`fullUrl`) which is an absolute URL conforming to the FHIR specification's [RESTful URL definition](https://www.hl7.org/fhir/references.html#regex), then the FHIR server will attempt to resolve the local reference as follows, based on the [FHIR specification](https://www.hl7.org/fhir/bundle.html#references): - it will extract the FHIR base URL from the local identifier and append the local reference to it - it will then try to resolve the reference within the request bundle using the updated local reference

    See the relative local reference example below for an illustration of this rule.

In the example above, you can see that there are two POST requests and the Patient request entry appears in the bundle before the Observation request entry. However, based on the order dependency processing rule, it would still be a valid request bundle even if the Observation request entry appeared before the Patient request entry.

The following examples also satisfy the local reference processing rules:

Example: Circular references

{
"resourceType" : "Bundle",
"type" : "batch",
"entry" : [ {
"fullUrl" : "urn:Encounter_1",
"resource" : {
"resourceType" : "Encounter",
"reasonReference" : [ {

While processing a request bundle, but before processing individual request entries, the IBM FHIR server detects the use of a local identifier within any POST or PUT request entry’s fullUrl field, and establishes a mapping between that local identifier and the corresponding external identifier that results from performing the POST or PUT operation.

Using the circular reference example above, the FHIR server detects the use of local identifiers in the Encounter request entry (urn:Encounter_1) and in the Procedure request entry (urn:Procedure_1), and establishes a mapping between the local identifiers and the external references to be associated with the new Encounter and Procedure resources (for example, Encounter/1cc5d299-d2be-4f93-8745-a121232ffe5b and Procedure/22b21fcf-8d00-492d-9de0-e25ddd409eaf).

Then when the FHIR server processes the POST requests for the Encounter and Procedure resources, it detects the use of the local references and substitutes the corresponding external references for them before creating the new resources. Below is the response bundle for the request bundle in the circular references example. We can see that the Encounter’s reasonReference.reference field now contains a proper external reference to the newly-created Procedure resource, and the Procedure’s encounter.reference field now contains a proper external reference to the newly-created Encounter resource:

{
"resourceType" : "Bundle",
"type" : "batch-response",
"entry" : [ {
"resource" : {
"resourceType" : "Encounter",
"id" : "1cc5d299-d2be-4f93-8745-a121232ffe5b",
"reasonReference" : [ {

Example: Reference to entry with conditional create

{
"resourceType" : "Bundle",
"type" : "batch",
"entry" : [ {
"resource" : {
"resourceType" : "Observation",
"id" : "25b1fe08-7612-45eb-af80-7e15d9806b2b",
"subject" : {

In the above example, even though the Patient request entry is a conditional create request, this is still a valid request bundle, because the FHIR server resolves any conditional requests before it establishes the mapping between local identifiers and the corresponding external identifiers that will result from performing the POST or PUT operation.

Example: Reference via relative local reference

{
"resourceType" : "Bundle",
"type" : "batch",
"entry" : [ {
"fullUrl" : "https://fhirserver1:9443/fhir-server/api/v4/Patient/new",
"resource" : {
"resourceType" : "Patient",
},

In this example, we demonstrate the relative reference processing rule. The local identifiers for the request bundle entries (https://fhirserver1:9443/fhir-server/api/v4/Patient/new and https://fhirserver1:9443/fhir-server/api/v4/Observation/new) are absolute URLs that conform to the FHIR specification’s RESTful URL definition . When the FHIR server attempts to resolve the Observation request entry’s subject reference (Patient/new), it will apply the processing rule for relative references, since the reference is a relative URL. The subject reference will be modified to be the original local reference (Patient/new) appended to the FHIR base URL extracted from the Observation entry’s local identifier (https://fhirserver1:9443/fhir-server/api/v4/). The resulting local reference (https://fhirserver1:9443/fhir-server/api/v4/Patient/new) will then be a valid reference to the Patient request bundle entry.

4.9 Multi-tenancy

The FHIR server includes features that allow a single instance of the server to simultaneously support multiple tenants. A tenant is defined as a group of one or more FHIR REST API consumers that share a FHIR server configuration along with one or more data stores associated with that configuration. A tenant could be a single application using the FHIR REST API, or it could be a group of applications belonging to a single customer. The main idea behind multi-tenancy is that each tenant can experience its own customized FHIR server runtime behavior and its data can be physically isolated from other tenants’ data for increased security and privacy.

4.9.1 Specifying the tenant id

To support multi-tenancy, the FHIR server must know which tenant an incoming REST API request is intended for. To provide the tenant id to the FHIR server, a REST API consumer must set a request header in each REST API request. The name of this request header is itself configurable by setting the fhirServer/core/tenantIdHeaderName configuration property in the FHIR server’s global configuration file (located at $⁠{server.config.dir}/config/default/fhir-server-config.json). The following example shows the default setting for this configuration parameter:

{
"fhirServer":{
"core":{
"tenantIdHeaderName":"X-FHIR-TENANT-ID"
},
}
}

With this configuration in place, each REST API consumer would need to set the X-FHIR-TENANT-ID request header to the appropriate tenant id. Each tenant’s tenant id value is assigned by the deployer. It is simply a short name associated with the tenant and must be unique among all tenants supported by a single FHIR server instance. For example, let’s suppose Acme Healthcare, Inc. is using the FHIR server and their tenant id has been assigned by the deployer as “acme”. In this case, Acme-related applications would set the following request header in each REST API request: X-FHIR-TENANT-ID: acme

As mentioned earlier, the name of the request header is configurable in the FHIR server’s global configuration file. For example, the FHIR server deployer could configure the request header name to be X-WHCLSF-tenant-id by setting tenantIdHeaderName as shown in the following example:

{
"fhirServer":{
"core":{
"tenantIdHeaderName":"X-WHCLSF-tenant-id"
},
}
}

This would be useful in an environment where the applications might already be using a similar type of request header. With this configuration in place, the “Acme Healthcare, Inc.” tenant would set the following request header in each REST API request: X-WHCLSF-tenant-id: acme

4.9.2 Configuration properties

The FHIR server allows a deployer to configure a subset of the supported configuration properties on a tenant-specific basis. For a complete list of configuration properties supported on a per-tenant basis, see Section 5.1.3 Property attributes.

When the FHIR server needs to retrieve any of the tenant-specific configuration properties, it does so dynamically each time the property value is needed. This means that a deployer can change the value of a tenant-specific property within a tenant’s configuration file on disk, and the FHIR server will immediately “see” the new value the next time it tries to retrieve it. For example, suppose the deployer initially defines the acme tenant’s fhir-server-config.json file such that the fhirServer/core/defaultPrettyPrint property is set to true.

Requests from the acme tenant would result in pretty-printed responses (with newlines and indentation), making it easier for humans to read. Now suppose the deployer changes the value of that property to true within the acme tenant’s fhir-server-config.json file. A subsequent REST API request would then see the output condensed into a single line with minimal whitespace.

4.9.2.1 Examples

This section contains examples of both a global (default) configuration and a tenant-specific configuration.

Global configuration (default)

The global configuration contains non-tenant specific configuration parameters (configuration parameters that are not resolved or used on a tenant-specific basis), as well as default values for tenant-specific configuration parameters.

${server.config.dir}/config/default/fhir-server-config.json

{
"__comment":"FHIR server global (default) configuration",
"fhirServer":{
"core":{
"defaultPrettyPrint":false,
"tenantIdHeaderName":"X-FHIR-TENANT-ID"
},
"notifications":{
"common":{
Acme Healthcare, Inc. (acme)

The Acme Healthcare, Inc tenant configuration overrides the fhirServer/core/defaultPrettyPrint property so that the development team can more easilly read the messages.

${server.config.dir}/config/acme/fhir-server-config.json

{
"__comment":"FHIR server configuration for tenant: Acme Healthcare, Inc.",
"fhirServer":{
"core":{
"defaultPrettyPrint":true
}
}
}

It is also possible to configure the persistence properties for a specific tenant, for example to set an alternate database hostname or database schema name.

4.10 Bulk data operations

The IBM FHIR Server implements bulk data export according to the HL7 FHIR BulkDataAccess IG: STU1, and bulk data import is implemented according to the Proposal for $import Operation.

There are 2 modules involved:

  • fhir-operation-bulkdata
  • fhir-bulkdata-webapp

The fhir-operation-bulkdata module implements the REST APIs for bulk data export, import and status as FHIR Operations. There are five operations:

OperationPath
ExportOperationsystem $export
PatientExportOperationPatient Patient/$export
GroupExportOperationGroup Group/[id]/$export
ImportOperationimport resources using the system endpoint, $import
StatusOperationpolling status for import and export $bulkdata-status

Each operation queues a job with the Open Liberty JavaBatch framework. Each job instance unit-of-work is executed as a job with the fhir-bulkdata-webapp module. There are 5 JavaBatch jobs defined in the fhir-bulkdata-webapp module for the above 3 export operations and 1 import operation:

Java Batch JobOperation
FhirBulkExportChunkJob$export
FhirBulkExportFastJob$export
FhirBulkExportPatientChunkJobPatient/$export
FhirBulkExportGroupChunkJobGroup/[id]/$export
FhirBulkImportChunkJob$import

The fhir-bulkdata-webapp module is a wrapper for the whole BulkData web application, which is the build artifact - fhir-bulkdata-webapp.war. This web archive is copied to the apps/ directory of the liberty server. The feature is configured using the configDropins/default/bulkdata.xml, such as:

<webApplication id="fhir-bulkdata-webapp" location="fhir-bulkdata-webapp.war" name="fhir-bulkdata-webapp">
<classloader privateLibraryRef="configResources,fhirUserLib"/>
<application-bnd>
<security-role id="users" name="FHIRUsers">
<group id="bulkUsersGroup" name="FHIRUsers"/>
</security-role>
</application-bnd>
</webApplication>

The Bulk Data web application writes the exported FHIR resources to an IBM Cloud Object Storage (COS), any Amazon S3-Compatible bucket (e.g, Amazon S3, minIO etc), or directory as configured in the per-tenant server configuration under fhirServer/bulkdata. The following is an example configuration for bulkdata; please refer to section 5 for the detailed description of these properties:

"bulkdata": {
"enabled": true,
"core": {
"api": {
"url": "https://localhost:9443/ibm/api/batch",
"user": "fhiradmin",
"password": "change-password",
"truststore": "resources/security/fhirTrustStore.p12",
"truststorePassword": "change-password"

Each tenant’s configuration may define multiple storageProviders. The default is assumed, unless specified with the X-FHIR-BULKDATA-PROVIDER and X-FHIR-BULKDATA-PROVIDER-OUTCOME. Each tenant’s configuration may mix the different providers, however each provider is only of a single type. For instance, minio is aws-s3 and default is file. Note, type http is only applicable to $import operations. Export is only supported with s3 and file.

To use Amazon S3 bucket for exporting, please set accessKeyId to S3 access key, and set secretAccessKey to the S3 secret key, and the auth type to hmac.

Basic system exports to S3 without typeFilters use a streamlined implementation which bypasses the IBM FHIR Server Search API for direct access to the data enabling better throughput. The fhirServer/bulkdata/core/systemExportImpl property can be used to disable the streamlined system export implementation. To use the legacy implementation based on IBM FHIR Server search, set the value to “legacy”. The new system export implementation is used by default for any export not using typeFilters. Exports using typeFilters use FHIR Search, and cannot use the streamlined export.

To import using the $import operation with https, one must additionally configure the fhirServer/bulkdata/storageProviders/(source)/validBaseUrls. For example, if one stores bulk data at https://test-url1.cos.ibm.com/bucket1/test.ndjson and https://test-url2.cos.ibm.com/bucket2/test2.ndjson you must specify both baseUrls in the configuration:

"validBaseUrls": [
"https://test-url1.cos.ibm.com/bucket1",
"https://test-url2.cos.ibm.com/bucket2"
]

These base urls are not checked when using cloud object store and bulk-import. If you need to disable the validBaseUrls feature you may add fhirServer/bulkdata/storageProviders/(source)/disableBaseUrlValidation as true.

For Bulk Data import, the fhirServer/bulkdata/core/maxInputs is used to configure a maximum number of inputs supported by the instance. The default number is 5. There is a hard character limit on the total input type and url must be under 4096 characters, as such the configuration may be tuned for each url scheme.

Note: When $import is executed, if a resource to import includes a Resource.id then this id is honored (via create-on-update). If Resource.id is not valued, the server will perform a create and assign a new Resource.id for this resource.

Following is the beautified response of sample polling location request after the export is finished:

{
"transactionTime": "2020/01/20 16:53:41.160 -0500",
"request": "https://myserver/fhir-server/api/v4/$export?_type=Patient",
"requiresAccessToken": false,
"output" : [
{ "type" : "AllergyIntolerance",
"url": "https://s3.us-south.cloud-object-storage.appdomain.cloud/fhir-bulkimexport-connectathon/6SfXzbGvYl1nTjGbf5qeqJDFNjyljiGdKxXEJb4yJn8=/AllergyIntolerance_1.ndjson",
"count": 20},
{ "type" : "AllergyIntolerance",

For the Import Operation, the polled status includes an indication of $import and the location of the OperationOutcome NDJSON files and the corresponding failure and success counts.

For S3 exports, the bulk data feature may use presigned urls. To enable presigned urls, the authentication type must be hmac and fhirServer/bulkdata/storageProviders/(source)/presigned must be true. The urls become:

https://s3.appdomain.cloud/fhir-example/Patient_1.ndjson?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=fcEXbf9cc1ac49e99eEX77%2F20210228%2Fus%2Fs3%2Faws4_request&X-Amz-Date=20210EXT160538Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=5ecfc207546b9737fd38d4f0EXEX1c58f4f82976338a44c

The presigned URL is valid for 86400 seconds (1 day).

Note, the deletion of an a job is split into two phases, ACCEPTED (202) response and DELETED (204). 202 is returned until the operation is stopped or removed, and then 204.

Prior to version 4.8.1, the exported ndjson file is configured with public access automatically and with 2 hours expiration time using fhirServer/bulkdata/storageProviders/(source)/exportPublic. The exported content is best made available with presigned urls with the hmac authentication type.

In 4.8.1, the randomly generated path is used to uniquely identify the exported files in a single folder.

JavaBatch feature must be enabled in server.xml as following on the Liberty server:

<featureManager>
...
<feature>batchManagement-1.0</feature>
...
</featureManager>

The JavaBatch user is configured in bulkdata.xml and the fhir-server-config.json:

<authorization-roles id="com.ibm.ws.batch">
<security-role name="batchAdmin">
<user name="fhiradmin"/>
</security-role>
<security-role name="batchSubmitter">
<user name="fhiruser"/>
</security-role>
<security-role name="batchMonitor">
<user name="fhiradmin"/>

Note: The batch-user referenced in the fhir-server-config.json must have a role of at least batchSubmitter.

By default, in-memory Derby database is used for persistence of the JavaBatch Jobs as configured in fhir-server/configDropins/defaults/bulkdata.xml. This database is destroyed on the restart of the IBM FHIR Server, and does not support load balancing.

To support IBM Db2 on IBM Cloud, copy fhir-server/configDropins/disabled/db2-cloud/bulkdata.xml to fhir-server/configDropins/defaults/bulkdata.xml replacing the existing bulkdata.xml. One can configure the Datasource by setting the following environment variables:

VariableDefaultDescription
BATCH_DB_HOSTNAMEblankThe hostname of the db2 instance
BATCH_DB_NAMEBLUDBThe database name
BATCH_DB_SCHEMAFHIR_JBATCHThe Schema Name configured to support the Java Batch framework
BATCH_DB_PORT50001The port configured to support the database
BATCH_DB_APIKEYblankThe API Key for the Db2 Cloud database

Instruction is also provided in ‘Configuring a Liberty Datasource with API Key’ section of the DB2OnCloudSetup guide to configure DB2 service in IBM Clouds as JavaBatch persistence store. The JavaBatch schema is created using the fhir-persistence-schema command line interface jar.

To support IBM Db2 with a user-name and password , copy fhir-server/configDropins/disabled/db2/bulkdata.xml to fhir-server/configDropins/defaults/bulkdata.xml replacing the existing bulkdata.xml. One can configure the Datasource by setting the following environment variables:

VariableDefaultDescription
BATCH_DB_HOSTNAMEblankThe hostname of the db2 instance
BATCH_DB_NAMEFHIRDBThe database name
BATCH_DB_SCHEMAFHIR_JBATCHThe Schema Name configured to support the Java Batch framework
BATCH_DB_PORT50000The port configured to support the database
BATCH_DB_USERdb2inst1The user for the Db2 database
BATCH_DB_PASSWORDblankThe password for the Db2 database
BATCH_DB_SSLtrueThe ssl connection is either true or false

If one wants to support Postgres with a user-name and password , one should copy fhir-server/configDropins/disabled/postgres/bulkdata.xml to fhir-server/configDropins/defaults/bulkdata.xml replacing the existing bulkdata.xml. One can configure the Datasource by setting the following environment variables:

VariableDefaultDescription
BATCH_DB_HOSTNAMEblankThe hostname of the postgres instance
BATCH_DB_NAMEFHIRDBThe database name
BATCH_DB_SCHEMAFHIR_JBATCHThe Schema Name configured to support the Java Batch framework
BATCH_DB_PORT5432The port configured to support the database
BATCH_DB_USERfhirserverThe user for the postgres database
BATCH_DB_PASSWORDblankThe password for the postgres database
BATCH_DB_SSLtrueThe ssl connection is either true or false
BATCH_DB_SSL_CERT_PATHfalseThe ssl connection is either true or false

Note: If you use PostgreSQL database as IBM FHIR Server data store or the JavaBatch job repository, please enable max_prepared_transactions in postgresql.conf, otherwise the import/export JavaBatch jobs fail.

For more information about Liberty JavaBatch configuration, please refer to IBM WebSphere Liberty Java Batch White paper.

4.10.1 Path and Virtual Host Bucket Access

For BulkData storage types of ibm-cos and aws-s3, the IBM FHIR Server supports two styles of accessing the s3 bucket - virtual host and path. In the IBM FHIR Server, path is the default access. Link

With path style access, the objects in a bucket are accessed using the pattern - https://s3.region.host-name.com/bucket-name/object-key. To configure path style access, one needs to configure the fhir-server-config.json.

There are three critical elements in the configuration to configure path style:

ConfigurationDetails
endpointInternalthe direct S3 API provider for the S3 API
endpointExternalthe endpoint url used to generate the downloadUrl used in S3 Export
accessType“path”, the default access type

Example of path based access:

"bulkdata": {
...
"storageProviders": {
"default" : {
"type": "aws-s3",
"bucketName": "bucket-name",
"location": "us",
"endpointInternal": "https://s3.region.host-name.com",
"endpointExternal": "https://s3.region.host-name.com",

With virtual host style access, the objects in a bucket are accessed using the pattern - https://bucket-name.s3.region.host-name.com/object-key. To configure virtual host style access, one needs to configure the API.

There are three critical elements in the configuration to configure virtual host style:

ConfigurationDetails
endpointInternalthe direct API provider for the S3 API, and not the virtual host, the underlying S3 libraries generate the virtual host url
endpointExternalthe Virtual Host endpoint url used to generate the downloadUrl generated after an Export
accessType“host”

Note, while the endpointInternal is specified with the S3 region endpoint, the calls to the API will use the virtual host directly.

Example of host based access:

"bulkdata"