Examples: FHIR Client
This page contains example interceptors that can be registered with the FHIR Client.
The following example shows an interceptor that can be used as a starter Client interceptor, implementing a hook method for each available pointcut.
/*-
* #%L
* Smile CDR - CDR
* %%
* Copyright (C) 2016 - 2024 Smile CDR, Inc.
* %%
* All rights reserved.
* #L%
*/
package com.smilecdr.demo.fhirclient;
import ca.cdr.api.i18n.ILocalizer;
import ca.cdr.api.transactionlog.ITransactionLogFetchingSvc;
import ca.cdr.api.transactionlog.ITransactionLogStoringSvc;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.util.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
* This interceptor is intended to be used with a FHIR client (IRestfulClient, or its implementations)
* and must be registered with it, before being called.
* <p>
* Example:
* IGenericClient fhirClient = FhirContext.forR4().newRestfulGenericClient("http://localhost:8000/");
* fhirClient.registerInterceptor(new ClientInterceptorTemplate());
* Afterward, calls done using 'fhirClient' will be intercepted by 'ClientInterceptorTemplate'
* and CLIENT_REQUEST and CLIENT_RESPONSE hook methods will be called.
* </p><p>
* Client interceptor can be useful to change the behaviour of a FHIR client before it send requests to a FHIR Endpoint,
* by changing the url/headers/content for example,
* or by doing some checks (on http status, headers, etc.) on the response received from a FHIR Endpoint.
* Currently, the response received can be modified in a limited way by the Client interceptor `CLIENT_RESPONSE` hook.
* More specifically, they can only override the HTTP response by invoking the setting on ClientResponseContext, the
* fourth parameter of the Pointcut.
* See ClientResponseInterceptorModificationTemplate for more details.
* </p><p>
* In fact, this is how the FHIR client BasicAuthInterceptor, BearerTokenAuthInterceptor, LoggingInterceptor,
* and others are actually implemented.
* </p>
* Can be used as a starting point for your client interceptor.
*/
@SuppressWarnings({"unused", "EmptyTryBlock"})
@Interceptor
public class ClientInterceptorTemplate {
private static final Logger ourLog = LoggerFactory.getLogger(ClientInterceptorTemplate.class);
@Hook(Pointcut.CLIENT_REQUEST)
public void clientRequest(
IHttpRequest theHttpRequest,
IRestfulClient theRestfulClient) {
ourLog.info("Interceptor CLIENT_REQUEST - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info("Interceptor CLIENT_REQUEST - ended, execution took {}", stopWatch);
}
}
@Hook(Pointcut.CLIENT_RESPONSE)
public void clientResponse(
IHttpRequest theHttpRequest,
IHttpResponse theHttpResponse,
IRestfulClient theRestfulClient) {
ourLog.info("Interceptor CLIENT_RESPONSE - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info("Interceptor CLIENT_RESPONSE - ended, execution took {}", stopWatch);
}
}
}
The following example shows an interceptor that can be used to modify an HTTP request, in this case, by wrapping an R4 Patient resource with a R4 Bundle.
/*-
* #%L
* Smile CDR - CDR
* %%
* Copyright (C) 2016 - 2024 Smile CDR, Inc.
* %%
* All rights reserved.
* #L%
*/
package com.smilecdr.demo.fhirclient;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.client.apache.ModifiedStringApacheHttpResponse;
import ca.uhn.fhir.rest.client.api.ClientResponseContext;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.util.StopWatch;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CapabilityStatement;
import org.hl7.fhir.r4.model.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.Nonnull;
import java.io.IOException;
import java.io.Reader;
/**
* This interceptor is intended to be used with a FHIR client (IRestfulClient, or its implementations)
* and must be registered with it, before being called.
* <p>
* Example:
* IGenericClient fhirClient = FhirContext.forR4().newRestfulGenericClient("http://localhost:8000/");
* fhirClient.registerInterceptor(new ClientInterceptorTemplate());
* Afterward, calls done using 'fhirClient' will be intercepted by 'ClientResponseInterceptorModificationTemplate'
* and CLIENT_REQUEST and CLIENT_RESPONSE hook methods will be called.
* </p>
* This particular interceptor illustrates how to take an existing HTTP response containing a FHIR resource and return
* a modified copy of that response that includes said Resource within a new Bundle.
*/
@Interceptor
public class ClientResponseInterceptorModificationTemplate {
private static final Logger ourLog = LoggerFactory.getLogger(ClientResponseInterceptorModificationTemplate.class);
@Hook(Pointcut.CLIENT_RESPONSE)
public void intercept(ClientResponseContext clientResponseContext) throws IOException {
final IBaseResource baseResourceVersionAgnostic = parseBaseResource(clientResponseContext);
if (baseResourceVersionAgnostic == null) {
ourLog.info("Ignoring since we could not parse the HTTP response for a Resource");
return;
}
if (!(baseResourceVersionAgnostic instanceof Resource)) {
ourLog.info("Ignoring since we could not parse the HTTP response for a Resource");
return;
}
final Class<? extends IBaseResource> returnType = clientResponseContext.getReturnType();
// We only want to trigger further processing if the expected return type is a Bundle
if (returnType != null && Bundle.class != returnType) {
ourLog.info("Ignoring since the expected return type is not a Bundle");
return;
}
// After this point, you must commit to a FHIR version, in this case: R4
final Resource resourceR4 = (Resource) baseResourceVersionAgnostic;
// If this is a Bundle do nothing
if (resourceR4 instanceof Bundle) {
ourLog.info("Ignoring since this resource is already a Bundle");
return;
}
final Bundle bundleR4 = bundleFromResourceR4(resourceR4);
final String bundleR4AsJson = jsonFromBundle(clientResponseContext.getFhirContext(), bundleR4);
// Wrap the old response in such a way as the new bundle JSON will be rendered instead of the old resource
final ModifiedStringApacheHttpResponse modifiedStringApacheHttpResponse =
new ModifiedStringApacheHttpResponse(clientResponseContext.getHttpResponse(), bundleR4AsJson, new StopWatch());
// Mutate the original response so that FHIR Gateway will now process the bundle in place of the patient
clientResponseContext.setHttpResponse(modifiedStringApacheHttpResponse);
}
private IBaseResource parseBaseResource(ClientResponseContext clientResponseContext) throws IOException {
final IHttpResponse httpResponse = clientResponseContext.getHttpResponse();
if (Constants.STATUS_HTTP_204_NO_CONTENT == httpResponse.getStatus()) {
ourLog.info("There is no content in this HTTP response so doing nothing");
return null;
}
final FhirContext fhirContext = clientResponseContext.getFhirContext();
final String mimeType = httpResponse.getMimeType();
final String body = getResponseBody(httpResponse);
if (Constants.CT_TEXT.equals(mimeType)) {
ourLog.info("There is no FHIR resource in this HTTP response so doing nothing");
return null;
}
final EncodingEnum enc = EncodingEnum.forContentType(mimeType);
if (enc == null) {
ourLog.info("Could not find a valid mime type in this HTTP response so doing nothing");
return null;
}
final IParser p = enc.newParser(fhirContext);
return p.parseResource(body);
}
@Nonnull
private Bundle bundleFromResourceR4(Resource iBaseResource) {
final Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.SEARCHSET);
bundle.setTotal(1);
final Bundle.BundleEntryComponent entry = bundle.addEntry();
entry.setResource(iBaseResource);
return bundle;
}
private String jsonFromBundle(FhirContext fhirContext, Bundle bundle) {
return fhirContext.newJsonParser()
.setPrettyPrint(true)
.encodeResourceToString(bundle);
}
private String getResponseBody(IHttpResponse theHttpResponse) throws IOException {
// It is CRUCIAL that you call the line below or downstream code in FHIR Gateway will be unable to close the InputStream
theHttpResponse.bufferEntity();
try (final Reader reader = theHttpResponse.createReader()) {
return IOUtils.toString(reader);
}
}
}