This page contains example interceptors that can be registered with Subscription.
The following interceptor can be used to add any authorization header, like Basic Auth or Bearer token, before calling a REST hook deliverer for each resource delivery message.
/*-
* #%L
* Smile CDR - CDR
* %%
* Copyright (C) 2016 - 2026 Smile CDR, Inc.
* %%
* All rights reserved.
* #L%
*/
package com.smilecdr.demo.subscription;
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.jpa.subscription.model.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
/**
* This class is a Hook Interceptor that is invoked immediately before REST HOOK
* deliveries. It demonstrates a simple OAuth2 Client Credentials flow to dynamically
* retrieve a Bearer token and inject it as an Authorization header
*/
@Interceptor
public class SubscriptionRestHookDynamicHeaderInterceptor {
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionRestHookDynamicHeaderInterceptor.class);
// TODO: Replace with your OAuth2 token endpoint URL
private static final String TOKEN_ENDPOINT = "https://your-auth-server.example.com/oauth2/token";
// TODO: Replace with your OAuth2 client_id
private static final String CLIENT_ID = "your-client-id";
// TODO: Replace with your OAuth2 client_secret
private static final String CLIENT_SECRET = "your-client-secret";
private final RestTemplate myRestTemplate = new RestTemplate();
/**
* The {@link Pointcut#SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY} is invoked immediately before each
* REST HOOK subscription delivery.
*
* @param theDeliveryMessage This object contains details about the subscription invocation, including
* the contents of the resource that will be delivered.
* @param theSubscription This object contains details about the subscription, including the delivery endpoint,
* headers to add, etc. A new copy of this object is created for every individual delivery
* so it is fine to modify it, and this will not affect other deliveries or
* subscriptions.
* @return Return <code>true</code> if the subscription should proceed, or <code>false</code> if it
* should be aborted.
*/
@Hook(Pointcut.SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY)
public boolean beforeRestHookDelivery(
ResourceDeliveryMessage theDeliveryMessage, CanonicalSubscription theSubscription) {
try {
final String accessToken = fetchAccessToken();
theSubscription.addHeader("Authorization: Bearer " + accessToken);
} catch (Exception theException) {
ourLog.error("Failed to fetch access token from {}; aborting subscription delivery", TOKEN_ENDPOINT, theException);
return false;
}
return true;
}
/**
* Performs an OAuth2 Client Credentials grant request
* and returns the raw access token string. This example is using
* client credentials secret flow but it can be updated to use
* client credentials jwt assertion flow
*/
@SuppressWarnings("unchecked")
protected String fetchAccessToken() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth(CLIENT_ID, CLIENT_SECRET);
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "client_credentials");
Map<String, Object> response = myRestTemplate.postForObject(
TOKEN_ENDPOINT, new HttpEntity<>(body, headers), Map.class);
if (response == null || !response.containsKey("access_token")) {
throw new RuntimeException("Token endpoint response did not contain an access_token field");
}
return (String) response.get("access_token");
}
}
The following example shows an interceptor that can be used as a starter Subscription interceptor, implementing a hook method for each available pointcut.
/*-
* #%L
* Smile CDR - CDR
* %%
* Copyright (C) 2016 - 2026 Smile CDR, Inc.
* %%
* All rights reserved.
* #L%
*/
package com.smilecdr.demo.subscription;
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.jpa.searchparam.matcher.InMemoryMatchResult;
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
import ca.uhn.fhir.util.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Sample Subscription Interceptor implementing all SUBSCRIPTION_XXX pointcuts.
* It is intended to be used in Subscription module 'Interceptor Bean Types'.
* Can be used as a starting point for your subscription interceptor.
*/
@SuppressWarnings({"unused", "EmptyTryBlock"})
@Interceptor
public class SubscriptionInterceptorTemplate {
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionInterceptorTemplate.class);
@Hook(Pointcut.SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED)
public void subscriptionAfterActiveSubscriptionRegistered(CanonicalSubscription theCanonicalSubscription) {
ourLog.info("Interceptor SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info(
"Interceptor SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED - ended, execution took {}",
stopWatch);
}
}
@Hook(Pointcut.SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_UNREGISTERED)
public void subscriptionAfterActiveSubscriptionUnregistered() {
ourLog.info("Interceptor SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_UNREGISTERED - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info(
"Interceptor SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_UNREGISTERED - ended, execution took {}",
stopWatch);
}
}
@Hook(Pointcut.SUBSCRIPTION_RESOURCE_MODIFIED)
public boolean subscriptionResourceModified(ResourceModifiedMessage theResourceModifiedMessage) {
ourLog.info("Interceptor SUBSCRIPTION_RESOURCE_MODIFIED - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info("Interceptor SUBSCRIPTION_RESOURCE_MODIFIED - ended, execution took {}", stopWatch);
}
return true;
}
@Hook(Pointcut.SUBSCRIPTION_RESOURCE_DID_NOT_MATCH_ANY_SUBSCRIPTIONS)
public void subscriptionResourceDidNotMatchAnySubscriptions(ResourceModifiedMessage theResourceModifiedMessage) {
ourLog.info("Interceptor SUBSCRIPTION_RESOURCE_DID_NOT_MATCH_ANY_SUBSCRIPTIONS - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info(
"Interceptor SUBSCRIPTION_RESOURCE_DID_NOT_MATCH_ANY_SUBSCRIPTIONS - ended, execution took {}",
stopWatch);
}
}
@Hook(Pointcut.SUBSCRIPTION_RESOURCE_MATCHED)
public boolean subscriptionResourceMatched(
CanonicalSubscription theCanonicalSubscription,
ResourceDeliveryMessage theResourceDeliveryMessage,
InMemoryMatchResult theInMemoryMatchResult) {
ourLog.info("Interceptor SUBSCRIPTION_RESOURCE_MATCHED - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info("Interceptor SUBSCRIPTION_RESOURCE_MATCHED - ended, execution took {}", stopWatch);
}
return true;
}
@Hook(Pointcut.SUBSCRIPTION_BEFORE_PERSISTED_RESOURCE_CHECKED)
public boolean subscriptionBeforePersistedResourceChecked(ResourceModifiedMessage theResourceModifiedMessage) {
ourLog.info("Interceptor SUBSCRIPTION_BEFORE_PERSISTED_RESOURCE_CHECKED - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info(
"Interceptor SUBSCRIPTION_BEFORE_PERSISTED_RESOURCE_CHECKED - ended, execution took {}", stopWatch);
}
return true;
}
@Hook(Pointcut.SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED)
public void subscriptionAfterPersistedResourceChecked(ResourceModifiedMessage theResourceModifiedMessage) {
ourLog.info("Interceptor SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info(
"Interceptor SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED - ended, execution took {}", stopWatch);
}
}
@Hook(Pointcut.SUBSCRIPTION_BEFORE_DELIVERY)
public boolean subscriptionBeforeDelivery(
CanonicalSubscription theCanonicalSubscription, ResourceDeliveryMessage theResourceDeliveryMessage) {
ourLog.info("Interceptor SUBSCRIPTION_BEFORE_DELIVERY - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info("Interceptor SUBSCRIPTION_BEFORE_DELIVERY - ended, execution took {}", stopWatch);
}
return true;
}
@Hook(Pointcut.SUBSCRIPTION_AFTER_DELIVERY)
public void subscriptionAfterDelivery(
CanonicalSubscription theCanonicalSubscription, ResourceDeliveryMessage theResourceDeliveryMessage) {
ourLog.info("Interceptor SUBSCRIPTION_AFTER_DELIVERY - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info("Interceptor SUBSCRIPTION_AFTER_DELIVERY - ended, execution took {}", stopWatch);
}
}
@Hook(Pointcut.SUBSCRIPTION_AFTER_DELIVERY_FAILED)
public boolean subscriptionAfterDeliveryFailed(
ResourceDeliveryMessage theResourceDeliveryMessage, Exception theException) {
ourLog.info("Interceptor SUBSCRIPTION_AFTER_DELIVERY_FAILED - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info("Interceptor SUBSCRIPTION_AFTER_DELIVERY_FAILED - ended, execution took {}", stopWatch);
}
return true;
}
@Hook(Pointcut.SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY)
public boolean subscriptionBeforeRestHookDelivery(
CanonicalSubscription theCanonicalSubscription, ResourceDeliveryMessage theResourceDeliveryMessage) {
ourLog.info("Interceptor SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info("Interceptor SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY - ended, execution took {}", stopWatch);
}
return true;
}
@Hook(Pointcut.SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY)
public void subscriptionAfterRestHookDelivery(
CanonicalSubscription theCanonicalSubscription, ResourceDeliveryMessage theResourceDeliveryMessage) {
ourLog.info("Interceptor SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info("Interceptor SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY - ended, execution took {}", stopWatch);
}
}
@Hook(Pointcut.SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY)
public boolean subscriptionBeforeMessageDelivery(
CanonicalSubscription theCanonicalSubscription,
ResourceDeliveryMessage theResourceDeliveryMessage,
ResourceModifiedJsonMessage theResourceModifiedJsonMessage) {
ourLog.info("Interceptor SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info("Interceptor SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY - ended, execution took {}", stopWatch);
}
return true;
}
@Hook(Pointcut.SUBSCRIPTION_AFTER_MESSAGE_DELIVERY)
public void subscriptionAfterMessageDelivery(
CanonicalSubscription theCanonicalSubscription, ResourceDeliveryMessage theResourceDeliveryMessage) {
ourLog.info("Interceptor SUBSCRIPTION_AFTER_MESSAGE_DELIVERY - started");
StopWatch stopWatch = new StopWatch();
try {
// your implementation goes here
} finally {
ourLog.info("Interceptor SUBSCRIPTION_AFTER_MESSAGE_DELIVERY - ended, execution took {}", stopWatch);
}
}
}