Smile CDR v2022.08.PRE
On this page:

11.3FHIR Storage Examples

 

This page contains example interceptors that can be registered with the FHIR Storage module.

11.3.1Example: Response 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 MDM 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 {

	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.
	 */
	@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
	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.
	 */
	@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
	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'll use a system property to pull the port number for this example. If the port
		// number won't change across deployments you don't need to do this.
		int port = Integer.parseInt(System.getProperty("mqm_query_port"));

		// 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:" + port + "/mdmquery?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"));
	}
}

11.3.2Example: Server-Reserved Tags

 

The following example shows an interceptor that implements reserved tags. For this example, a reserved tag is a tag that can only be set or removed by the server itself. These tags can not be added or removed by a client, and are hidden from the client as well.

/**
 * This interceptor implements the concept of "reserved tags", which are resource tags (tags in
 * <code>Resource.meta.tag</code>) that are reserved for internal system use. This means that:
 * <ul>
 *    <li>Clients can not add these tags manually</li>
 *    <li>Reserved tags are preserved between updates of resources that have them</li>
 *    <li>The reserved tags are not shown to clients and are filtered before serialization</li>
 * </ul>
 *
 * The assumption with this interceptor is that there is a separate process that manages resource tags.
 * This could be a second interceptor, or some other non-client-facing process.
 *
 * Note that each of the hook methods are annotated with {@literal @Hook(..., order=100)}. The assumption
 * here is that a separate interceptor is managing the reserved tags. That interceptor could use
 * {@literal @Hook(..., order=101)} to ensure that it comes afterward so that it can add or remove
 * the reserved tags as necessary.
 */
@SuppressWarnings("unchecked")
public class TagPreservingInterceptor extends ServerOperationInterceptorAdapter {

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

	private Set<Pair> myTagsToPreserve;

	/**
	 * Constructor
	 */
	public TagPreservingInterceptor() {
		// This is the list of tags we want to preserve
		myTagsToPreserve = Collections.unmodifiableSet(Sets.newHashSet(
			Pair.of("http://my-tags", "tag1"),
			Pair.of("http://my-tags", "tag2")
		));
	}

	/**
	 * When a new resource is being created, don't allow the client to include any reserved tags
	 *
	 * @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 theResource The previous resource being created
	 */
	@Hook(value = Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, order = 100)
	public void resourcePreCreate(RequestDetails theRequest, IBaseResource theResource) {
		removeReservedTags(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.
	 */
	@Hook(value = Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, order = 100)
	public void resourcePreUpdate(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {

		// Filter out any reserved tags
		removeReservedTags(theNewResource);

		// Copy tags forward
		theOldResource
			.getMeta()
			.getTag()
			.stream()
			.filter(t -> myTagsToPreserve.contains(Pair.of(t.getSystem(), t.getCode())))
			.forEach(t -> theNewResource.getMeta().addTag()
				.setSystem(t.getSystem())
				.setCode(t.getCode())
				.setDisplay(t.getDisplay()));
	}

	/**
	 * Before returning any resource to the client, strip the reserved tags
	 */
	@Hook(value = Pointcut.STORAGE_PRESHOW_RESOURCES)
	public void preShow(IPreResourceShowDetails thePreShowDetails) {
		for (IBaseResource nextResource : thePreShowDetails) {
			removeReservedTags(nextResource);
		}
	}

	private void removeReservedTags(IBaseResource theResource) {
		for (Iterator<? extends IBaseCoding> iter = theResource.getMeta().getTag().iterator(); iter.hasNext(); ) {
			IBaseCoding nextTag = iter.next();
			String system = nextTag.getSystem();
			String code = nextTag.getCode();
			if (myTagsToPreserve.contains(Pair.of(system, code))) {
				iter.remove();
			}
		}
	}

}

11.3.3Example: JavaScript Storage Interceptor

 
function fhirResourcePreCreate(theRequestDetails, theResource) {
   if (theResource.name[0].family === "initial-family-name") {
      theResource.name[0].family = "after-pre-create";
   }
}

function fhirResourceCreated(theRequestDetails, theResource) {
   if (theResource.name[0].family === "after-pre-create") {
      throw "the resource was created";
   }
}

function fhirResourcePreUpdate(theRequestDetails, theOldResource, theNewResource) {
   if (theNewResource.resourceType === 'Patient') {
      if (theOldResource.name[0].family === 'old-name' && theNewResource.name[0].family === 'new-name') {
         theNewResource.name[0].given[0] = 'a-given-name';
      }
   } else {
      Log.info("Unknown resource type " + theNewResource.resourceType);
   }
}

function fhirResourceUpdated(theRequestDetails, theOldResource, theNewResource) {
   if (theOldResource.name[0].family === 'old-name' && theNewResource.name[0].given[0].toString() === "a-given-name") {
      throw "the resource was updated";
   }
}

function fhirResourcePreDelete(theRequestDetails, theResource) {
   if (theResource.name[0].family === 'removed-family-name') {
      theResource.name[0].given[0] = 'b-given-name';
   }
}


function fhirResourceDeleted(theRequestDetails, theResource) {
   if (theResource.resourceType === 'Patient') {
      throw "This patient was deleted";
   }
}