3817 enhance subscription before message delivery (#3824)
* Add changelog * implementation and changes * collection implementation * tidy
This commit is contained in:
parent
f5697e13c9
commit
0a1e782a8e
|
@ -871,11 +871,15 @@ public enum Pointcut implements IPointcut {
|
||||||
* Hooks may make changes to the delivery payload, or make changes to the
|
* Hooks may make changes to the delivery payload, or make changes to the
|
||||||
* canonical subscription such as adding headers, modifying the channel
|
* canonical subscription such as adding headers, modifying the channel
|
||||||
* endpoint, etc.
|
* endpoint, etc.
|
||||||
|
* Furthermore, you may modify the outgoing message wrapper, for example adding headers via ResourceModifiedJsonMessage field.
|
||||||
|
*
|
||||||
* </p>
|
* </p>
|
||||||
* Hooks may accept the following parameters:
|
* Hooks may accept the following parameters:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription</li>
|
* <li>ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription</li>
|
||||||
* <li>ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage</li>
|
* <li>ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage</li>
|
||||||
|
* <li>ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage</li>
|
||||||
|
*
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* Hooks may return <code>void</code> or may return a <code>boolean</code>. If the method returns
|
* Hooks may return <code>void</code> or may return a <code>boolean</code>. If the method returns
|
||||||
|
@ -883,7 +887,7 @@ public enum Pointcut implements IPointcut {
|
||||||
* returns <code>false</code>, processing will be aborted.
|
* returns <code>false</code>, processing will be aborted.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY(boolean.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage"),
|
SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY(boolean.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage", "ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage"),
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
type: add
|
||||||
|
issue: 3817
|
||||||
|
title: "The 'SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY' pointcut now supports a new parameter, `ResourceModifiedJsonMessage`. This permits interceptor implementers to modify the outgoing envelope before it is sent off. "
|
|
@ -29,6 +29,7 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription;
|
import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription;
|
||||||
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry;
|
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry;
|
||||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
|
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryJsonMessage;
|
||||||
import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage;
|
import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
|
@ -32,11 +32,9 @@ import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage;
|
||||||
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
|
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
|
||||||
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
|
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.messaging.MessagingException;
|
import org.springframework.messaging.MessagingException;
|
||||||
|
|
||||||
|
@ -57,31 +55,30 @@ public class SubscriptionDeliveringMessageSubscriber extends BaseSubscriptionDel
|
||||||
myChannelFactory = theChannelFactory;
|
myChannelFactory = theChannelFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, IChannelProducer theChannelProducer) {
|
protected void doDelivery(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, IChannelProducer theChannelProducer, ResourceModifiedJsonMessage theMessage) {
|
||||||
IBaseResource payloadResource = theMsg.getPayload(myFhirContext);
|
theChannelProducer.send(theMessage);
|
||||||
|
ourLog.debug("Delivering {} message payload {} for {}", theMsg.getOperationType(), theMsg.getPayloadId(), theSubscription.getIdElement(myFhirContext).toUnqualifiedVersionless().getValue());
|
||||||
// Regardless of whether we have a payload, the message should be sent.
|
|
||||||
doDelivery(theMsg, theSubscription, theChannelProducer, payloadResource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doDelivery(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, IChannelProducer theChannelProducer, IBaseResource thePayloadResource) {
|
private ResourceModifiedJsonMessage convertDeliveryMessageToResourceModifiedMessage(ResourceDeliveryMessage theMsg) {
|
||||||
|
IBaseResource thePayloadResource = theMsg.getPayload(myFhirContext);
|
||||||
ResourceModifiedMessage payload = new ResourceModifiedMessage(myFhirContext, thePayloadResource, theMsg.getOperationType());
|
ResourceModifiedMessage payload = new ResourceModifiedMessage(myFhirContext, thePayloadResource, theMsg.getOperationType());
|
||||||
payload.setMessageKey(theMsg.getMessageKeyOrNull());
|
payload.setMessageKey(theMsg.getMessageKeyOrNull());
|
||||||
payload.setTransactionId(theMsg.getTransactionId());
|
payload.setTransactionId(theMsg.getTransactionId());
|
||||||
payload.setPartitionId(theMsg.getRequestPartitionId());
|
payload.setPartitionId(theMsg.getRequestPartitionId());
|
||||||
ResourceModifiedJsonMessage message = new ResourceModifiedJsonMessage(payload);
|
return new ResourceModifiedJsonMessage(payload);
|
||||||
theChannelProducer.send(message);
|
|
||||||
ourLog.debug("Delivering {} message payload {} for {}", theMsg.getOperationType(), theMsg.getPayloadId(), theSubscription.getIdElement(myFhirContext).toUnqualifiedVersionless().getValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(ResourceDeliveryMessage theMessage) throws MessagingException, URISyntaxException {
|
public void handleMessage(ResourceDeliveryMessage theMessage) throws MessagingException, URISyntaxException {
|
||||||
CanonicalSubscription subscription = theMessage.getSubscription();
|
CanonicalSubscription subscription = theMessage.getSubscription();
|
||||||
|
ResourceModifiedJsonMessage messageWrapperToSend = convertDeliveryMessageToResourceModifiedMessage(theMessage);
|
||||||
|
|
||||||
// Interceptor call: SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY
|
// Interceptor call: SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY
|
||||||
HookParams params = new HookParams()
|
HookParams params = new HookParams()
|
||||||
.add(CanonicalSubscription.class, subscription)
|
.add(CanonicalSubscription.class, subscription)
|
||||||
.add(ResourceDeliveryMessage.class, theMessage);
|
.add(ResourceDeliveryMessage.class, theMessage)
|
||||||
|
.add(ResourceModifiedJsonMessage.class, messageWrapperToSend);
|
||||||
if (!getInterceptorBroadcaster().callHooks(Pointcut.SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY, params)) {
|
if (!getInterceptorBroadcaster().callHooks(Pointcut.SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY, params)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -106,7 +103,7 @@ public class SubscriptionDeliveringMessageSubscriber extends BaseSubscriptionDel
|
||||||
throw new UnsupportedOperationException(Msg.code(4) + "Only JSON payload type is currently supported for Message Subscriptions");
|
throw new UnsupportedOperationException(Msg.code(4) + "Only JSON payload type is currently supported for Message Subscriptions");
|
||||||
}
|
}
|
||||||
|
|
||||||
deliverPayload(theMessage, subscription, channelProducer);
|
doDelivery(theMessage, subscription, channelProducer, messageWrapperToSend);
|
||||||
|
|
||||||
// Interceptor call: SUBSCRIPTION_AFTER_MESSAGE_DELIVERY
|
// Interceptor call: SUBSCRIPTION_AFTER_MESSAGE_DELIVERY
|
||||||
params = new HookParams()
|
params = new HookParams()
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.subscription.match.deliver;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
@ -27,6 +28,7 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.Answers;
|
import org.mockito.Answers;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.ArgumentMatchers;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -38,10 +40,15 @@ import org.springframework.messaging.support.GenericMessage;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
@ -171,6 +178,41 @@ public class BaseSubscriptionDeliverySubscriberTest {
|
||||||
verify(myGenericClient, times(1)).update();
|
verify(myGenericClient, times(1)).update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMessageSubscriber_PermitsInterceptorsToModifyOutgoingEnvelope() throws URISyntaxException {
|
||||||
|
|
||||||
|
//Given: We setup mocks, and have this mock interceptor inject those headers.
|
||||||
|
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY), ArgumentMatchers.any(HookParams.class))).thenAnswer(t -> {
|
||||||
|
HookParams argument = t.getArgument(1, HookParams.class);
|
||||||
|
ResourceModifiedJsonMessage resourceModifiedJsonMessage = argument.get(ResourceModifiedJsonMessage.class);
|
||||||
|
resourceModifiedJsonMessage.getHapiHeaders().getCustomHeaders().put("foo", List.of("bar", "bar2"));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.SUBSCRIPTION_AFTER_MESSAGE_DELIVERY), any())).thenReturn(false);
|
||||||
|
when(myChannelFactory.getOrCreateProducer(any(), any(), any())).thenReturn(myChannelProducer);
|
||||||
|
|
||||||
|
CanonicalSubscription subscription = generateSubscription();
|
||||||
|
Patient patient = generatePatient();
|
||||||
|
|
||||||
|
ResourceDeliveryMessage payload = new ResourceDeliveryMessage();
|
||||||
|
payload.setSubscription(subscription);
|
||||||
|
payload.setPayload(myCtx, patient, EncodingEnum.JSON);
|
||||||
|
payload.setOperationType(ResourceModifiedMessage.OperationTypeEnum.CREATE);
|
||||||
|
|
||||||
|
//When: We submit the message for delivery
|
||||||
|
myMessageSubscriber.handleMessage(payload);
|
||||||
|
|
||||||
|
//Then: The receiving channel should also receive the custom headers.
|
||||||
|
ArgumentCaptor<ResourceModifiedJsonMessage> captor = ArgumentCaptor.forClass(ResourceModifiedJsonMessage.class);
|
||||||
|
verify(myChannelProducer).send(captor.capture());
|
||||||
|
final List<ResourceModifiedJsonMessage> messages = captor.getAllValues();
|
||||||
|
assertThat(messages, hasSize(1));
|
||||||
|
ResourceModifiedJsonMessage receivedMessage = messages.get(0);
|
||||||
|
Collection<String> foo = (Collection<String>) receivedMessage.getHapiHeaders().getCustomHeaders().get("foo");
|
||||||
|
|
||||||
|
assertThat(foo, containsInAnyOrder("bar", "bar2"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRestHookDeliveryAbortedByInterceptor() {
|
public void testRestHookDeliveryAbortedByInterceptor() {
|
||||||
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.SUBSCRIPTION_BEFORE_DELIVERY), any())).thenReturn(true);
|
when(myInterceptorBroadcaster.callHooks(eq(Pointcut.SUBSCRIPTION_BEFORE_DELIVERY), any())).thenReturn(true);
|
||||||
|
|
|
@ -83,6 +83,9 @@ public class HapiMessageHeaders implements IModelJson {
|
||||||
|
|
||||||
|
|
||||||
public Map<String, Object> getCustomHeaders() {
|
public Map<String, Object> getCustomHeaders() {
|
||||||
|
if (this.headers == null) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
return this.headers;
|
return this.headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue