On this page:

5.13Interceptors

 

The HAPI FHIR Server Interceptor Framework is a mechanism for modifying the behavior of the server in a variety of ways.

As described in the documentation, special classes called Interceptors can be used to:

  • Pre-process or mask data that is being returned to the client as a result of a FHIR search, read, etc.
  • Add additional business checks before processing a write operation
  • Modify data that was submitted by a client prior to saving it

5.13.1Coding and Deploying Interceptors

 

Interceptors can be registered in two different places:

  • You can register interceptors against the FHIR Endpoint module in the configuration for that module. Interceptors hook methods registered against the FHIR Endpoint module will be invoked for all incoming requests, and will also be passed to the FHIR Storage module for processing those events.
  • You can register interceptors against the FHIR Storage module in the configuration for that module. Interceptors registered against the FHIR Storage module will be applied to all requests against the storage module regardless of where they come from (i.e. if multiple FHIR Endpoint modules are linked to a single FHIR Storage module, these interceptor hook methods will apply to requests from all of them.

The following table outlines the various module types and the various types of Pointcuts that may be intercepted by an interceptor registered against that module.

Module Type Allowable Pointcuts
FHIR Endpoint
  • INTERCEPTOR_xxx
  • SERVER_xxx
  • STORAGE_xxx
  • JPA_PERFTRACE_xxx
FHIR Storage
  • INTERCEPTOR_xxx
  • STORAGE_xxx
  • SUBSCRIPTION_xxx
  • JPA_PERFTRACE_xxx

Registering An Interceptor

HAPI FHIR interceptors should be packaged as a simple JAR file containing only the classes defined by the interceptor (i.e. library dependencies such as Spring or Commons-Lang should not be included in your JAR).

The interceptor class (or classes) that you have defined should then be set on the interceptor_bean_types property within the appropriate module configuration.

For example, the following property sets a custom interceptor from within the configuration property file:

module.persistence.config.interceptor_bean_types=com.example.demo.ExampleAttributeEnhancingInterceptor

Interceptor classes have access to any of the standard libraries available within Smile CDR. This includes Spring Framework, Apache HTTPClient, Gson, Jackson, Woodstox, and others.

5.13.2Starter Project

 

The cdr-persistence-interceptordemoproject contains a complete example Maven-based Java project to define an interceptor that can be imported into Smile CDR. It is available for download at the following links:

5.13.3Example: Attribute Enhancement

 

The following example shows an interceptor that can be used to enhance resources that are submitted for update by a client. In this example, for every time that a Patient resource is created or updated, we are calling a REST EMPI service to fetch an EUID (an enterprise identifier for a Patient). We are then adding this EUID as an additional identifier on the list of patient identifiers.

/**
 * This example interceptor fetches a set of attributes from a third-party
 * REST service and uses them to enhance the resource being submitted.
 */
@SuppressWarnings("unchecked")
public class ExampleAttributeEnhancingInterceptor extends ServerOperationInterceptorAdapter {

	private static final Logger ourLog = LoggerFactory.getLogger(ExampleAttributeEnhancingInterceptor.class);

	/**
	 * We are going to treat creates and updates the same way in this example, so the
	 * create method redirects to the update method.
	 */
	@Override
	public void resourcePreCreate(RequestDetails theRequest, IBaseResource theResource) {
		resourcePreUpdate(theRequest, null, theResource);
	}

	/**
	 * For this example we are overriding the <code>resourcePreUpdate</code>
	 * method, which will be called when a resource is being updated. There
	 * are many other methods that can be overridden, but this one is
	 * appropriate for the desired functionality.
	 *
	 * @param theRequest     Contains details about the incoming request. You may wish to cast this
	 *                       object to a {@link ca.uhn.fhir.rest.server.servlet.ServletRequestDetails},
	 *                       which is always possible when running interceptors inside Smile CDR.
	 * @param theOldResource The previous version of the resource, before this update was
	 *                       requested by the client.
	 * @param theNewResource The new version of the resource as submitted by the client. This
	 *                       object can be modified.
	 */
	@Override
	public void resourcePreUpdate(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {

		// This interceptor only cares about Patient resources
		if (!(theNewResource instanceof Patient)) {
			return;
		}

		Patient newResource = (Patient) theNewResource;

		/*
		 * We want to know the primary identifier, which is the first instance of
		 * Patient.identifier where identifer.system = "http://example.com/mrn". If the
		 * client has submitted a resourece that does not have such an identifier, this
		 * is an error and we will throw a PreconditionFailedException (HTTP 412)
		 */
		String primaryIdentifier = newResource
			.getIdentifier()
			.stream()
			.filter(t -> t.getSystem().equals("http://example.com/mrn"))
			.map(t -> t.getValue())
			.findFirst()
			.orElseThrow(() -> new PreconditionFailedException("No MRN supplied with request"));

		// We're going to make a web service call to a simple web service that
		// returns a JSON response. There are lots of ways of doing this, but
		// in our example we'll make a call using the Spring RestTemplate
		RestTemplate restTempate = new RestTemplate();
		restTempate.setMessageConverters(Collections.singletonList(new GsonHttpMessageConverter()));
		HashMap<String, Object> response = restTempate.getForObject("http://localhost:9999/empiquery?mrn=" + primaryIdentifier, HashMap.class);

		// We'll log the response. Generally this isn't a good idea in a real
		// production system but it's handy for debugging at first.
		ourLog.info("Response: {}", response);

		// Grab the existing EUID (identifier with a system of "http://example.com/euid")
		// and create one if it doesn't exist
		Identifier euidIdentifier = newResource
			.getIdentifier()
			.stream()
			.filter(t -> t.getSystem().equals("http://example.com/euid"))
			.findFirst()
			.orElseGet(() -> newResource.addIdentifier());

		// Set the identifier value using the web service response
		euidIdentifier.setSystem("http://example.com/euid");
		euidIdentifier.setValue((String) response.get("euid"));
	}
}