Empty, id-only content Payload types are not supported for Topic based Subscriptions (#5499)

* added support of Empty, id-only content Payload types for Topic based Subscriptions
This commit is contained in:
volodymyr-korzh 2023-12-07 09:05:51 -07:00 committed by GitHub
parent 78a50f92bc
commit 4d0670b0ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 720 additions and 188 deletions

View File

@ -196,11 +196,7 @@ public class BundleBuilder {
public UpdateBuilder addTransactionUpdateEntry(IBaseResource theResource) {
Validate.notNull(theResource, "theResource must not be null");
IIdType id = theResource.getIdElement();
if (id.hasIdPart() && !id.hasResourceType()) {
String resourceType = myContext.getResourceType(theResource);
id = id.withResourceType(resourceType);
}
IIdType id = getIdTypeForUpdate(theResource);
String requestUrl = id.toUnqualifiedVersionless().getValue();
String fullUrl = id.getValue();
@ -225,13 +221,29 @@ public class BundleBuilder {
myEntryRequestUrlChild.getMutator().setValue(request, url);
// Bundle.entry.request.method
IPrimitiveType<?> method = (IPrimitiveType<?>)
myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments());
method.setValueAsString(theHttpVerb);
myEntryRequestMethodChild.getMutator().setValue(request, method);
addRequestMethod(request, theHttpVerb);
return url;
}
/**
* Adds an entry containing an update (UPDATE) request without the body of the resource.
* Also sets the Bundle.type value to "transaction" if it is not already set.
*
* @param theResource The resource to update.
*/
public void addTransactionUpdateIdOnlyEntry(IBaseResource theResource) {
setBundleField("type", "transaction");
Validate.notNull(theResource, "theResource must not be null");
IIdType id = getIdTypeForUpdate(theResource);
String requestUrl = id.toUnqualifiedVersionless().getValue();
String fullUrl = id.getValue();
String httpMethod = "PUT";
addIdOnlyEntry(requestUrl, httpMethod, fullUrl);
}
/**
* Adds an entry containing an create (POST) request.
* Also sets the Bundle.type value to "transaction" if it is not already set.
@ -247,20 +259,47 @@ public class BundleBuilder {
String resourceType = myContext.getResourceType(theResource);
// Bundle.entry.request.url
IPrimitiveType<?> url =
(IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance();
url.setValueAsString(resourceType);
myEntryRequestUrlChild.getMutator().setValue(request, url);
addRequestUrl(request, resourceType);
// Bundle.entry.request.url
IPrimitiveType<?> method = (IPrimitiveType<?>)
myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments());
method.setValueAsString("POST");
myEntryRequestMethodChild.getMutator().setValue(request, method);
// Bundle.entry.request.method
addRequestMethod(request, "POST");
return new CreateBuilder(request);
}
/**
* Adds an entry containing a create (POST) request without the body of the resource.
* Also sets the Bundle.type value to "transaction" if it is not already set.
*
* @param theResource The resource to create
*/
public void addTransactionCreateEntryIdOnly(IBaseResource theResource) {
setBundleField("type", "transaction");
String requestUrl = myContext.getResourceType(theResource);
String fullUrl = theResource.getIdElement().getValue();
String httpMethod = "POST";
addIdOnlyEntry(requestUrl, httpMethod, fullUrl);
}
private void addIdOnlyEntry(String theRequestUrl, String theHttpMethod, String theFullUrl) {
IBase entry = addEntry();
// Bundle.entry.request
IBase request = myEntryRequestDef.newInstance();
myEntryRequestChild.getMutator().setValue(entry, request);
// Bundle.entry.request.url
addRequestUrl(request, theRequestUrl);
// Bundle.entry.request.method
addRequestMethod(request, theHttpMethod);
// Bundle.entry.fullUrl
addFullUrl(entry, theFullUrl);
}
/**
* Adds an entry containing a delete (DELETE) request.
* Also sets the Bundle.type value to "transaction" if it is not already set.
@ -341,20 +380,44 @@ public class BundleBuilder {
IBase request = addEntryAndReturnRequest();
// Bundle.entry.request.url
IPrimitiveType<?> url =
(IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance();
url.setValueAsString(theDeleteUrl);
myEntryRequestUrlChild.getMutator().setValue(request, url);
addRequestUrl(request, theDeleteUrl);
// Bundle.entry.request.method
IPrimitiveType<?> method = (IPrimitiveType<?>)
myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments());
method.setValueAsString("DELETE");
myEntryRequestMethodChild.getMutator().setValue(request, method);
addRequestMethod(request, "DELETE");
return new DeleteBuilder();
}
private IIdType getIdTypeForUpdate(IBaseResource theResource) {
IIdType id = theResource.getIdElement();
if (id.hasIdPart() && !id.hasResourceType()) {
String resourceType = myContext.getResourceType(theResource);
id = id.withResourceType(resourceType);
}
return id;
}
private void addFullUrl(IBase theEntry, String theFullUrl) {
IPrimitiveType<?> fullUrl =
(IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance();
fullUrl.setValueAsString(theFullUrl);
myEntryFullUrlChild.getMutator().setValue(theEntry, fullUrl);
}
private void addRequestUrl(IBase request, String theRequestUrl) {
IPrimitiveType<?> url =
(IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance();
url.setValueAsString(theRequestUrl);
myEntryRequestUrlChild.getMutator().setValue(request, url);
}
private void addRequestMethod(IBase theRequest, String theMethod) {
IPrimitiveType<?> method = (IPrimitiveType<?>)
myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments());
method.setValueAsString(theMethod);
myEntryRequestMethodChild.getMutator().setValue(theRequest, method);
}
/**
* Adds an entry for a Collection bundle type
*/
@ -406,10 +469,7 @@ public class BundleBuilder {
IBase entry = addEntry();
// Bundle.entry.fullUrl
IPrimitiveType<?> fullUrl =
(IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance();
fullUrl.setValueAsString(theFullUrl);
myEntryFullUrlChild.getMutator().setValue(entry, fullUrl);
addFullUrl(entry, theFullUrl);
// Bundle.entry.resource
myEntryResourceChild.getMutator().setValue(entry, theResource);

View File

@ -0,0 +1,5 @@
---
type: add
issue: 5498
title: "Added support for `id-only` and `empty` payload content types for notifications
triggered by R5, R4B, and R4 back-ported topic subscriptions."

View File

@ -23,12 +23,14 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription;
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
import ca.uhn.fhir.jpa.topic.status.INotificationStatusBuilder;
import ca.uhn.fhir.jpa.topic.status.R4BNotificationStatusBuilder;
import ca.uhn.fhir.jpa.topic.status.R4NotificationStatusBuilder;
import ca.uhn.fhir.jpa.topic.status.R5NotificationStatusBuilder;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.util.BundleBuilder;
import org.apache.commons.lang3.ObjectUtils;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.Bundle;
@ -37,6 +39,8 @@ import org.slf4j.LoggerFactory;
import java.util.List;
import static org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.FULLRESOURCE;
public class SubscriptionTopicPayloadBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTopicPayloadBuilder.class);
private final FhirContext myFhirContext;
@ -73,7 +77,7 @@ public class SubscriptionTopicPayloadBuilder {
myNotificationStatusBuilder.buildNotificationStatus(theResources, theActiveSubscription, theTopicUrl);
bundleBuilder.addCollectionEntry(notificationStatus);
addResources(bundleBuilder, theResources, theRestOperationType);
addResources(theResources, theActiveSubscription.getSubscription(), theRestOperationType, bundleBuilder);
// WIP STR5 add support for notificationShape include, revinclude
// Note we need to set the bundle type after we add the resources since adding the resources automatically sets
@ -87,7 +91,46 @@ public class SubscriptionTopicPayloadBuilder {
return retval;
}
private static void addResources(
private void addResources(
List<IBaseResource> theResources,
CanonicalSubscription theCanonicalSubscription,
RestOperationTypeEnum theRestOperationType,
BundleBuilder theBundleBuilder) {
org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent content =
ObjectUtils.defaultIfNull(theCanonicalSubscription.getContent(), FULLRESOURCE);
switch (content) {
case EMPTY:
// skip adding resource to the Bundle
break;
case IDONLY:
addIdOnly(theBundleBuilder, theResources, theRestOperationType);
break;
case FULLRESOURCE:
addFullResources(theBundleBuilder, theResources, theRestOperationType);
break;
}
}
private void addIdOnly(
BundleBuilder bundleBuilder, List<IBaseResource> theResources, RestOperationTypeEnum theRestOperationType) {
for (IBaseResource resource : theResources) {
switch (theRestOperationType) {
case CREATE:
bundleBuilder.addTransactionCreateEntryIdOnly(resource);
break;
case UPDATE:
bundleBuilder.addTransactionUpdateIdOnlyEntry(resource);
break;
case DELETE:
bundleBuilder.addTransactionDeleteEntry(resource);
break;
}
}
}
private void addFullResources(
BundleBuilder bundleBuilder, List<IBaseResource> theResources, RestOperationTypeEnum theRestOperationType) {
for (IBaseResource resource : theResources) {
switch (theRestOperationType) {

View File

@ -20,6 +20,7 @@
package ca.uhn.fhir.jpa.topic;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
import ca.uhn.fhir.rest.server.messaging.BaseResourceMessage;
import ca.uhn.fhir.util.BundleUtil;
@ -72,4 +73,14 @@ public class SubscriptionTopicUtil {
.findFirst()
.orElse(null);
}
/**
* Checks if {@link CanonicalSubscription} has EMPTY {@link org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent}
* Used for R5/R4B/R4 Notification Status object building.
*/
public static boolean isEmptyContentTopicSubscription(CanonicalSubscription theCanonicalSubscription) {
return theCanonicalSubscription.isTopicSubscription()
&& org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.EMPTY
== theCanonicalSubscription.getTopicSubscription().getContent();
}
}

View File

@ -21,6 +21,8 @@ package ca.uhn.fhir.jpa.topic.status;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription;
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
import ca.uhn.fhir.jpa.topic.SubscriptionTopicUtil;
import ca.uhn.fhir.subscription.SubscriptionConstants;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CanonicalType;
@ -46,6 +48,7 @@ public class R4NotificationStatusBuilder implements INotificationStatusBuilder<P
public Parameters buildNotificationStatus(
List<IBaseResource> theResources, ActiveSubscription theActiveSubscription, String theTopicUrl) {
Long eventNumber = theActiveSubscription.getDeliveriesCount();
CanonicalSubscription canonicalSubscription = theActiveSubscription.getSubscription();
// See http://build.fhir.org/ig/HL7/fhir-subscription-backport-ig/Parameters-r4-notification-status.json.html
// and
@ -66,12 +69,12 @@ public class R4NotificationStatusBuilder implements INotificationStatusBuilder<P
notificationEvent.setName("notification-event");
notificationEvent.addPart().setName("event-number").setValue(new StringType(eventNumber.toString()));
notificationEvent.addPart().setName("timestamp").setValue(new DateType(new Date()));
if (theResources.size() > 0) {
if (!theResources.isEmpty() && !SubscriptionTopicUtil.isEmptyContentTopicSubscription(canonicalSubscription)) {
IBaseResource firstResource = theResources.get(0);
notificationEvent
.addPart()
.setName("focus")
.setValue(new Reference(firstResource.getIdElement().toUnqualifiedVersionless()));
Reference resourceReference =
new Reference(firstResource.getIdElement().toUnqualifiedVersionless());
notificationEvent.addPart().setName("focus").setValue(resourceReference);
}
return parameters;

View File

@ -21,6 +21,8 @@ package ca.uhn.fhir.jpa.topic.status;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription;
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
import ca.uhn.fhir.jpa.topic.SubscriptionTopicUtil;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.r5.model.Reference;
@ -40,6 +42,7 @@ public class R5NotificationStatusBuilder implements INotificationStatusBuilder<S
public SubscriptionStatus buildNotificationStatus(
List<IBaseResource> theResources, ActiveSubscription theActiveSubscription, String theTopicUrl) {
long eventNumber = theActiveSubscription.getDeliveriesCount();
CanonicalSubscription canonicalSubscription = theActiveSubscription.getSubscription();
SubscriptionStatus subscriptionStatus = new SubscriptionStatus();
subscriptionStatus.setId(UUID.randomUUID().toString());
@ -50,7 +53,7 @@ public class R5NotificationStatusBuilder implements INotificationStatusBuilder<S
SubscriptionStatus.SubscriptionStatusNotificationEventComponent event =
subscriptionStatus.addNotificationEvent();
event.setEventNumber(eventNumber);
if (theResources.size() > 0) {
if (!theResources.isEmpty() && !SubscriptionTopicUtil.isEmptyContentTopicSubscription(canonicalSubscription)) {
event.setFocus(new Reference(theResources.get(0).getIdElement()));
}
subscriptionStatus.setSubscription(

View File

@ -8,74 +8,177 @@ import ca.uhn.fhir.util.BundleUtil;
import org.hl7.fhir.r4b.model.Bundle;
import org.hl7.fhir.r4b.model.Encounter;
import org.hl7.fhir.r4b.model.Resource;
import org.junit.jupiter.api.Test;
import org.hl7.fhir.r4b.model.SubscriptionStatus;
import org.hl7.fhir.r5.model.Subscription;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SubscriptionTopicPayloadBuilderR4BTest {
private static final String TEST_TOPIC_URL = "test-builder-topic-url";
FhirContext ourFhirContext = FhirContext.forR4BCached();
@Test
public void testBuildPayloadDelete() {
private SubscriptionTopicPayloadBuilder myStPayloadBuilder;
private Encounter myEncounter;
private CanonicalSubscription myCanonicalSubscription;
private ActiveSubscription myActiveSubscription;
@BeforeEach
void before() {
myStPayloadBuilder = new SubscriptionTopicPayloadBuilder(ourFhirContext);
myEncounter = new Encounter();
myEncounter.setId("Encounter/1");
myCanonicalSubscription = new CanonicalSubscription();
myCanonicalSubscription.setTopicSubscription(true);
myActiveSubscription = new ActiveSubscription(myCanonicalSubscription, "test");
}
@ParameterizedTest
@ValueSource(strings = {
"full-resource",
"" // payload content not provided
})
public void testBuildPayload_deleteWithFullResourceContent_returnsCorrectPayload(String thePayloadContent) {
// setup
var svc = new SubscriptionTopicPayloadBuilder(ourFhirContext);
var encounter = new Encounter();
encounter.setId("Encounter/1");
CanonicalSubscription sub = new CanonicalSubscription();
ActiveSubscription subscription = new ActiveSubscription(sub, "test");
Subscription.SubscriptionPayloadContent payloadContent =
Subscription.SubscriptionPayloadContent.fromCode(thePayloadContent);
myCanonicalSubscription.getTopicSubscription().setContent(payloadContent);
// run
Bundle payload = (Bundle)svc.buildPayload(List.of(encounter), subscription, TEST_TOPIC_URL, RestOperationTypeEnum.DELETE);
Bundle payload = (Bundle) myStPayloadBuilder.buildPayload(List.of(myEncounter), myActiveSubscription, TEST_TOPIC_URL, RestOperationTypeEnum.DELETE);
// verify
// verify Bundle size
assertEquals(2, payload.getEntry().size());
List<Resource> resources = BundleUtil.toListOfResourcesOfType(ourFhirContext, payload, Resource.class);
assertEquals(1, resources.size());
assertEquals("SubscriptionStatus", resources.get(0).getResourceType().name());
assertEquals(Bundle.HTTPVerb.DELETE, payload.getEntry().get(1).getRequest().getMethod());
// verify SubscriptionStatus.notificationEvent.focus
verifySubscriptionStatusNotificationEvent(resources.get(0));
// verify Encounter entry
Bundle.BundleEntryComponent encounterEntry = payload.getEntry().get(1);
assertNull(encounterEntry.getResource());
assertNull(encounterEntry.getFullUrl());
verifyRequestParameters(encounterEntry, org.hl7.fhir.r5.model.Bundle.HTTPVerb.DELETE.name(), "Encounter/1");
}
@Test
public void testBuildPayloadUpdate() {
@ParameterizedTest
@CsvSource({
"create, POST , full-resource, Encounter/1, Encounter",
"update, PUT , full-resource, Encounter/1, Encounter/1",
"create, POST , , Encounter/1, Encounter",
"update, PUT , , Encounter/1, Encounter/1",
})
public void testBuildPayload_createUpdateWithFullResourceContent_returnsCorrectPayload(String theRestOperationType,
String theHttpMethod,
String thePayloadContent,
String theFullUrl,
String theRequestUrl) {
// setup
var svc = new SubscriptionTopicPayloadBuilder(ourFhirContext);
var encounter = new Encounter();
encounter.setId("Encounter/1");
CanonicalSubscription sub = new CanonicalSubscription();
ActiveSubscription subscription = new ActiveSubscription(sub, "test");
Subscription.SubscriptionPayloadContent payloadContent =
Subscription.SubscriptionPayloadContent.fromCode(thePayloadContent);
myCanonicalSubscription.getTopicSubscription().setContent(payloadContent);
RestOperationTypeEnum restOperationType = RestOperationTypeEnum.forCode(theRestOperationType);
// run
Bundle payload = (Bundle)svc.buildPayload(List.of(encounter), subscription, TEST_TOPIC_URL, RestOperationTypeEnum.UPDATE);
Bundle payload = (Bundle) myStPayloadBuilder.buildPayload(List.of(myEncounter), myActiveSubscription, TEST_TOPIC_URL, restOperationType);
// verify
// verify Bundle size
assertEquals(2, payload.getEntry().size());
List<Resource> resources = BundleUtil.toListOfResourcesOfType(ourFhirContext, payload, Resource.class);
assertEquals(2, resources.size());
assertEquals("SubscriptionStatus", resources.get(0).getResourceType().name());
assertEquals("Encounter", resources.get(1).getResourceType().name());
assertEquals(Bundle.HTTPVerb.PUT, payload.getEntry().get(1).getRequest().getMethod());
// verify SubscriptionStatus.notificationEvent.focus
verifySubscriptionStatusNotificationEvent(resources.get(0));
// verify Encounter entry
Bundle.BundleEntryComponent encounterEntry = payload.getEntry().get(1);
assertEquals("Encounter", resources.get(1).getResourceType().name());
assertEquals(myEncounter, resources.get(1));
assertEquals(theFullUrl, encounterEntry.getFullUrl());
verifyRequestParameters(encounterEntry, theHttpMethod, theRequestUrl);
}
@Test
public void testBuildPayloadCreate() {
@ParameterizedTest
@CsvSource({
"create, POST , Encounter/1, Encounter",
"update, PUT , Encounter/1, Encounter/1",
"delete, DELETE, , Encounter/1"
})
public void testBuildPayload_withIdOnlyContent_returnsCorrectPayload(String theRestOperationType,
String theHttpMethod, String theFullUrl,
String theRequestUrl) {
// setup
var svc = new SubscriptionTopicPayloadBuilder(ourFhirContext);
var encounter = new Encounter();
encounter.setId("Encounter/1");
CanonicalSubscription sub = new CanonicalSubscription();
ActiveSubscription subscription = new ActiveSubscription(sub, "test");
myCanonicalSubscription.getTopicSubscription().setContent(Subscription.SubscriptionPayloadContent.IDONLY);
RestOperationTypeEnum restOperationType = RestOperationTypeEnum.forCode(theRestOperationType);
// run
Bundle payload = (Bundle)svc.buildPayload(List.of(encounter), subscription, TEST_TOPIC_URL, RestOperationTypeEnum.CREATE);
Bundle payload = (Bundle) myStPayloadBuilder.buildPayload(List.of(myEncounter), myActiveSubscription, TEST_TOPIC_URL, restOperationType);
// verify
// verify Bundle size
assertEquals(2, payload.getEntry().size());
List<Resource> resources = BundleUtil.toListOfResourcesOfType(ourFhirContext, payload, Resource.class);
assertEquals(2, resources.size());
assertEquals("SubscriptionStatus", resources.get(0).getResourceType().name());
assertEquals("Encounter", resources.get(1).getResourceType().name());
assertEquals(1, resources.size());
assertEquals(Bundle.HTTPVerb.POST, payload.getEntry().get(1).getRequest().getMethod());
// verify SubscriptionStatus.notificationEvent.focus
verifySubscriptionStatusNotificationEvent(resources.get(0));
// verify Encounter entry
Bundle.BundleEntryComponent encounterEntry = payload.getEntry().get(1);
assertNull(encounterEntry.getResource());
assertEquals(theFullUrl, encounterEntry.getFullUrl());
verifyRequestParameters(encounterEntry, theHttpMethod, theRequestUrl);
}
@ParameterizedTest
@CsvSource({
"create",
"update",
"delete"
})
public void testBuildPayload_withEmptyContent_returnsCorrectPayload(String theRestOperationType) {
// setup
myCanonicalSubscription.getTopicSubscription().setContent(Subscription.SubscriptionPayloadContent.EMPTY);
RestOperationTypeEnum restOperationType = RestOperationTypeEnum.forCode(theRestOperationType);
// run
Bundle payload = (Bundle) myStPayloadBuilder.buildPayload(List.of(myEncounter), myActiveSubscription, TEST_TOPIC_URL, restOperationType);
// verify Bundle size
assertEquals(1, payload.getEntry().size());
List<Resource> resources = BundleUtil.toListOfResourcesOfType(ourFhirContext, payload, Resource.class);
assertEquals(1, resources.size());
// verify SubscriptionStatus.notificationEvent.focus
assertEquals("SubscriptionStatus", resources.get(0).getResourceType().name());
assertEquals(1, ((SubscriptionStatus) resources.get(0)).getNotificationEvent().size());
SubscriptionStatus.SubscriptionStatusNotificationEventComponent notificationEvent =
((SubscriptionStatus) resources.get(0)).getNotificationEventFirstRep();
assertFalse(notificationEvent.hasFocus());
}
private void verifyRequestParameters(Bundle.BundleEntryComponent theEncounterEntry,
String theHttpMethod, String theRequestUrl) {
assertNotNull(theEncounterEntry.getRequest());
assertEquals(theHttpMethod, theEncounterEntry.getRequest().getMethod().name());
assertEquals(theRequestUrl, theEncounterEntry.getRequest().getUrl());
}
private void verifySubscriptionStatusNotificationEvent(Resource theResource) {
assertEquals("SubscriptionStatus", theResource.getResourceType().name());
assertEquals(1, ((SubscriptionStatus) theResource).getNotificationEvent().size());
SubscriptionStatus.SubscriptionStatusNotificationEventComponent notificationEvent =
((SubscriptionStatus) theResource).getNotificationEventFirstRep();
assertTrue(notificationEvent.hasFocus());
assertEquals(myEncounter.getId(), notificationEvent.getFocus().getReference());
}
}

View File

@ -8,74 +8,177 @@ import ca.uhn.fhir.util.BundleUtil;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Encounter;
import org.hl7.fhir.r5.model.Resource;
import org.junit.jupiter.api.Test;
import org.hl7.fhir.r5.model.Subscription;
import org.hl7.fhir.r5.model.SubscriptionStatus;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SubscriptionTopicPayloadBuilderR5Test {
private static final String TEST_TOPIC_URL = "test-builder-topic-url";
FhirContext ourFhirContext = FhirContext.forR5Cached();
@Test
public void testBuildPayloadDelete() {
// setup
var svc = new SubscriptionTopicPayloadBuilder(ourFhirContext);
var encounter = new Encounter();
encounter.setId("Encounter/1");
CanonicalSubscription sub = new CanonicalSubscription();
ActiveSubscription subscription = new ActiveSubscription(sub, "test");
private static final String TEST_TOPIC_URL = "test-builder-topic-url";
FhirContext ourFhirContext = FhirContext.forR5Cached();
private SubscriptionTopicPayloadBuilder myStPayloadBuilder;
private Encounter myEncounter;
private CanonicalSubscription myCanonicalSubscription;
private ActiveSubscription myActiveSubscription;
// run
Bundle payload = (Bundle)svc.buildPayload(List.of(encounter), subscription, TEST_TOPIC_URL, RestOperationTypeEnum.DELETE);
@BeforeEach
void before() {
myStPayloadBuilder = new SubscriptionTopicPayloadBuilder(ourFhirContext);
myEncounter = new Encounter();
myEncounter.setId("Encounter/1");
myCanonicalSubscription = new CanonicalSubscription();
myCanonicalSubscription.setTopicSubscription(true);
myActiveSubscription = new ActiveSubscription(myCanonicalSubscription, "test");
}
// verify
List<Resource> resources = BundleUtil.toListOfResourcesOfType(ourFhirContext, payload, Resource.class);
assertEquals(1, resources.size());
assertEquals("SubscriptionStatus", resources.get(0).getResourceType().name());
@ParameterizedTest
@ValueSource(strings = {
"full-resource",
"" // payload content not provided
})
public void testBuildPayload_deleteWithFullResourceContent_returnsCorrectPayload(String thePayloadContent) {
// setup
Subscription.SubscriptionPayloadContent payloadContent =
Subscription.SubscriptionPayloadContent.fromCode(thePayloadContent);
myCanonicalSubscription.getTopicSubscription().setContent(payloadContent);
assertEquals(Bundle.HTTPVerb.DELETE, payload.getEntry().get(1).getRequest().getMethod());
}
// run
Bundle payload = (Bundle) myStPayloadBuilder.buildPayload(List.of(myEncounter), myActiveSubscription, TEST_TOPIC_URL, RestOperationTypeEnum.DELETE);
@Test
public void testBuildPayloadUpdate() {
// setup
var svc = new SubscriptionTopicPayloadBuilder(ourFhirContext);
var encounter = new Encounter();
encounter.setId("Encounter/1");
CanonicalSubscription sub = new CanonicalSubscription();
ActiveSubscription subscription = new ActiveSubscription(sub, "test");
// verify Bundle size
assertEquals(2, payload.getEntry().size());
List<Resource> resources = BundleUtil.toListOfResourcesOfType(ourFhirContext, payload, Resource.class);
assertEquals(1, resources.size());
// run
Bundle payload = (Bundle)svc.buildPayload(List.of(encounter), subscription, TEST_TOPIC_URL, RestOperationTypeEnum.UPDATE);
// verify SubscriptionStatus.notificationEvent.focus
verifySubscriptionStatusNotificationEvent(resources.get(0));
// verify
List<Resource> resources = BundleUtil.toListOfResourcesOfType(ourFhirContext, payload, Resource.class);
assertEquals(2, resources.size());
assertEquals("SubscriptionStatus", resources.get(0).getResourceType().name());
assertEquals("Encounter", resources.get(1).getResourceType().name());
// verify Encounter entry
Bundle.BundleEntryComponent encounterEntry = payload.getEntry().get(1);
assertNull(encounterEntry.getResource());
assertNull(encounterEntry.getFullUrl());
verifyRequestParameters(encounterEntry, Bundle.HTTPVerb.DELETE.name(), "Encounter/1");
}
assertEquals(Bundle.HTTPVerb.PUT, payload.getEntry().get(1).getRequest().getMethod());
}
@ParameterizedTest
@CsvSource({
"create, POST , full-resource, Encounter/1, Encounter",
"update, PUT , full-resource, Encounter/1, Encounter/1",
"create, POST , , Encounter/1, Encounter",
"update, PUT , , Encounter/1, Encounter/1",
})
public void testBuildPayload_createUpdateWithFullResourceContent_returnsCorrectPayload(String theRestOperationType,
String theHttpMethod,
String thePayloadContent,
String theFullUrl,
String theRequestUrl) {
// setup
Subscription.SubscriptionPayloadContent payloadContent =
Subscription.SubscriptionPayloadContent.fromCode(thePayloadContent);
@Test
public void testBuildPayloadCreate() {
// setup
var svc = new SubscriptionTopicPayloadBuilder(ourFhirContext);
var encounter = new Encounter();
encounter.setId("Encounter/1");
CanonicalSubscription sub = new CanonicalSubscription();
ActiveSubscription subscription = new ActiveSubscription(sub, "test");
myCanonicalSubscription.getTopicSubscription().setContent(payloadContent);
RestOperationTypeEnum restOperationType = RestOperationTypeEnum.forCode(theRestOperationType);
// run
Bundle payload = (Bundle)svc.buildPayload(List.of(encounter), subscription, TEST_TOPIC_URL, RestOperationTypeEnum.CREATE);
// run
Bundle payload = (Bundle) myStPayloadBuilder.buildPayload(List.of(myEncounter), myActiveSubscription, TEST_TOPIC_URL, restOperationType);
// verify
List<Resource> resources = BundleUtil.toListOfResourcesOfType(ourFhirContext, payload, Resource.class);
assertEquals(2, resources.size());
assertEquals("SubscriptionStatus", resources.get(0).getResourceType().name());
assertEquals("Encounter", resources.get(1).getResourceType().name());
// verify Bundle size
assertEquals(2, payload.getEntry().size());
List<Resource> resources = BundleUtil.toListOfResourcesOfType(ourFhirContext, payload, Resource.class);
assertEquals(2, resources.size());
assertEquals(Bundle.HTTPVerb.POST, payload.getEntry().get(1).getRequest().getMethod());
}
// verify SubscriptionStatus.notificationEvent.focus
verifySubscriptionStatusNotificationEvent(resources.get(0));
// verify Encounter entry
Bundle.BundleEntryComponent encounterEntry = payload.getEntry().get(1);
assertEquals("Encounter", resources.get(1).getResourceType().name());
assertEquals(myEncounter, resources.get(1));
assertEquals(theFullUrl, encounterEntry.getFullUrl());
verifyRequestParameters(encounterEntry, theHttpMethod, theRequestUrl);
}
@ParameterizedTest
@CsvSource({
"create, POST , Encounter/1, Encounter",
"update, PUT , Encounter/1, Encounter/1",
"delete, DELETE, , Encounter/1"
})
public void testBuildPayload_withIdOnlyContent_returnsCorrectPayload(String theRestOperationType,
String theHttpMethod, String theFullUrl,
String theRequestUrl) {
// setup
myCanonicalSubscription.getTopicSubscription().setContent(Subscription.SubscriptionPayloadContent.IDONLY);
RestOperationTypeEnum restOperationType = RestOperationTypeEnum.forCode(theRestOperationType);
// run
Bundle payload = (Bundle) myStPayloadBuilder.buildPayload(List.of(myEncounter), myActiveSubscription, TEST_TOPIC_URL, restOperationType);
// verify Bundle size
assertEquals(2, payload.getEntry().size());
List<Resource> resources = BundleUtil.toListOfResourcesOfType(ourFhirContext, payload, Resource.class);
assertEquals(1, resources.size());
// verify SubscriptionStatus.notificationEvent.focus
verifySubscriptionStatusNotificationEvent(resources.get(0));
// verify Encounter entry
Bundle.BundleEntryComponent encounterEntry = payload.getEntry().get(1);
assertNull(encounterEntry.getResource());
assertEquals(theFullUrl, encounterEntry.getFullUrl());
verifyRequestParameters(encounterEntry, theHttpMethod, theRequestUrl);
}
@ParameterizedTest
@CsvSource({
"create",
"update",
"delete"
})
public void testBuildPayload_withEmptyContent_returnsCorrectPayload(String theRestOperationType) {
// setup
myCanonicalSubscription.getTopicSubscription().setContent(Subscription.SubscriptionPayloadContent.EMPTY);
RestOperationTypeEnum restOperationType = RestOperationTypeEnum.forCode(theRestOperationType);
// run
Bundle payload = (Bundle) myStPayloadBuilder.buildPayload(List.of(myEncounter), myActiveSubscription, TEST_TOPIC_URL, restOperationType);
// verify Bundle size
assertEquals(1, payload.getEntry().size());
List<Resource> resources = BundleUtil.toListOfResourcesOfType(ourFhirContext, payload, Resource.class);
assertEquals(1, resources.size());
// verify SubscriptionStatus.notificationEvent.focus
assertEquals("SubscriptionStatus", resources.get(0).getResourceType().name());
assertEquals(1, ((SubscriptionStatus) resources.get(0)).getNotificationEvent().size());
SubscriptionStatus.SubscriptionStatusNotificationEventComponent notificationEvent =
((SubscriptionStatus) resources.get(0)).getNotificationEventFirstRep();
assertFalse(notificationEvent.hasFocus());
}
private void verifyRequestParameters(Bundle.BundleEntryComponent theEncounterEntry,
String theHttpMethod, String theRequestUrl) {
assertNotNull(theEncounterEntry.getRequest());
assertEquals(theHttpMethod, theEncounterEntry.getRequest().getMethod().name());
assertEquals(theRequestUrl, theEncounterEntry.getRequest().getUrl());
}
private void verifySubscriptionStatusNotificationEvent(Resource theResource) {
assertEquals("SubscriptionStatus", theResource.getResourceType().name());
assertEquals(1, ((SubscriptionStatus) theResource).getNotificationEvent().size());
SubscriptionStatus.SubscriptionStatusNotificationEventComponent notificationEvent =
((SubscriptionStatus) theResource).getNotificationEventFirstRep();
assertTrue(notificationEvent.hasFocus());
assertEquals(myEncounter.getId(), notificationEvent.getFocus().getReference());
}
}

View File

@ -1,6 +1,9 @@
package ca.uhn.fhir.jpa.topic;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription;
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.model.CanonicalTopicSubscription;
import ca.uhn.fhir.rest.server.messaging.BaseResourceMessage;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.Bundle;
@ -8,9 +11,12 @@ import org.hl7.fhir.r5.model.Enumeration;
import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.model.Reference;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.Subscription;
import org.hl7.fhir.r5.model.SubscriptionStatus;
import org.hl7.fhir.r5.model.SubscriptionTopic;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import java.util.List;
@ -21,6 +27,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class SubscriptionTopicUtilTest {
private static final String TEST_CHANNEL_NAME = "TEST_CHANNEL";
private final FhirContext myContext = FhirContext.forR5Cached();
@Test
@ -86,4 +94,32 @@ class SubscriptionTopicUtilTest {
IBaseResource extractionResult = SubscriptionTopicUtil.extractResourceFromBundle(myContext, new Bundle());
assertNull(extractionResult);
}
@Test
public void testIsEmptyContentTopicSubscription_withEmptySubscription_returnsFalse() {
CanonicalSubscription canonicalSubscription = new CanonicalSubscription();
boolean result = SubscriptionTopicUtil.isEmptyContentTopicSubscription(canonicalSubscription);
assertFalse(result);
}
@ParameterizedTest
@CsvSource({
"full-resource, false",
"id-only , false",
"empty , true",
" , false",
})
public void testIsEmptyContentTopicSubscription_withContentPayload_returnsExpectedResult(String thePayloadContent,
boolean theExpectedResult) {
CanonicalTopicSubscription canonicalTopicSubscription = new CanonicalTopicSubscription();
canonicalTopicSubscription.setContent(Subscription.SubscriptionPayloadContent.fromCode(thePayloadContent));
CanonicalSubscription canonicalSubscription = new CanonicalSubscription();
canonicalSubscription.setTopicSubscription(canonicalTopicSubscription);
canonicalSubscription.setTopicSubscription(true);
boolean actualResult = SubscriptionTopicUtil.isEmptyContentTopicSubscription(canonicalSubscription);
assertEquals(theExpectedResult, actualResult);
}
}

View File

@ -5,7 +5,6 @@ import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionsR4Test;
import ca.uhn.fhir.jpa.subscription.submit.svc.ResourceModifiedSubmitterSvc;
import ca.uhn.fhir.jpa.test.util.StoppableSubscriptionDeliveringRestHookSubscriber;
import ca.uhn.fhir.jpa.test.util.SubscriptionTestUtil;
import ca.uhn.fhir.jpa.topic.SubscriptionTopicDispatcher;
import ca.uhn.fhir.jpa.topic.SubscriptionTopicRegistry;
import ca.uhn.fhir.model.primitive.IdDt;
@ -15,6 +14,7 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.subscription.SubscriptionTestDataHelper;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.HapiExtensions;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IIdType;
@ -29,11 +29,12 @@ import org.hl7.fhir.r4.model.Meta;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.SearchParameter;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.Subscription;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@ -42,6 +43,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -66,6 +68,8 @@ import static org.junit.jupiter.api.Assertions.fail;
*/
public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(RestHookTestR4Test.class);
public static final String TEST_PATIENT_ID = "topic-test-patient-id";
public static final String PATIENT_REFERENCE = "Patient/" + TEST_PATIENT_ID;
@Autowired
ResourceModifiedSubmitterSvc myResourceModifiedSubmitterSvc;
@ -1306,8 +1310,73 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
}
@Test
public void testTopicSubscription() throws Exception {
Subscription subscription = SubscriptionTestDataHelper.buildR4TopicSubscription();
public void testRestHoodTopicSubscription_withEmptyPayloadContent_generateCorrectPayload() throws Exception {
String payloadContent = "empty";
// execute
Bundle bundle = createAndDispatchTopicSubscription(payloadContent);
// verify Bundle size
assertEquals(1, bundle.getEntry().size());
List<Resource> resources = BundleUtil.toListOfResourcesOfType(myFhirContext, bundle, Resource.class);
assertEquals(1, resources.size());
// verify SubscriptionStatus.notificationEvent.focus
Optional<Parameters.ParametersParameterComponent> focus = getNotificationEventFocus(resources);
assertFalse(focus.isPresent());
}
@Test
public void testRestHoodTopicSubscription_withIdOnlyPayloadContent_generateCorrectPayload() throws Exception {
String payloadContent = "id-only";
// execute
Bundle bundle = createAndDispatchTopicSubscription(payloadContent);
// verify Bundle size
assertEquals(2, bundle.getEntry().size());
List<Resource> resources = BundleUtil.toListOfResourcesOfType(myFhirContext, bundle, Resource.class);
assertEquals(1, resources.size());
// verify SubscriptionStatus.notificationEvent.focus
Optional<Parameters.ParametersParameterComponent> focus = getNotificationEventFocus(resources);
assertTrue(focus.isPresent());
assertEquals(TEST_PATIENT_ID, ((Reference) focus.get().getValue()).getReference());
// verify Patient Entry
Bundle.BundleEntryComponent patientEntry = bundle.getEntry().get(1);
validateRequestParameters(patientEntry);
Patient bundlePatient = (Patient) patientEntry.getResource();
assertNull(bundlePatient);
}
@Test
public void testRestHoodTopicSubscription_withFullResourcePayloadContent_generateCorrectPayload() throws Exception {
String payloadContent = "full-resource";
// execute
Bundle bundle = createAndDispatchTopicSubscription(payloadContent);
// verify Bundle size
assertEquals(2, bundle.getEntry().size());
List<Resource> resources = BundleUtil.toListOfResourcesOfType(myFhirContext, bundle, Resource.class);
assertEquals(2, resources.size());
// verify SubscriptionStatus.notificationEvent.focus
Optional<Parameters.ParametersParameterComponent> focus = getNotificationEventFocus(resources);
assertTrue(focus.isPresent());
assertEquals(PATIENT_REFERENCE, ((Reference) focus.get().getValue()).getReference());
// verify Patient Entry
Bundle.BundleEntryComponent patientEntry = bundle.getEntry().get(1);
validateRequestParameters(patientEntry);
Patient bundlePatient = (Patient) patientEntry.getResource();
assertTrue(bundlePatient.getActive());
assertEquals(Enumerations.AdministrativeGender.FEMALE, bundlePatient.getGender());
}
private Bundle createAndDispatchTopicSubscription(String thePayloadContent) throws Exception {
Subscription subscription = SubscriptionTestDataHelper.buildR4TopicSubscriptionWithContent(thePayloadContent);
subscription.setIdElement(null);
subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED);
Subscription.SubscriptionChannelComponent channel = subscription.getChannel();
@ -1319,9 +1388,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
mySubscriptionIds.add(methodOutcome.getId());
waitForActivatedSubscriptionCount(1);
String patientId = "topic-test-patient-id";
Patient patient = new Patient();
patient.setId(patientId);
patient.setId(TEST_PATIENT_ID);
patient.setActive(true);
patient.setGender(Enumerations.AdministrativeGender.FEMALE);
@ -1332,12 +1400,22 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
ourTransactionProvider.waitForTransactionCount(1);
Bundle bundle = ourTransactionProvider.getTransactions().get(0);
assertEquals(2, bundle.getEntry().size());
Parameters parameters = (Parameters) bundle.getEntry().get(0).getResource();
// WIP STR5 assert parameters contents
Patient bundlePatient = (Patient) bundle.getEntry().get(1).getResource();
assertTrue(bundlePatient.getActive());
assertEquals(Enumerations.AdministrativeGender.FEMALE, bundlePatient.getGender());
return ourTransactionProvider.getTransactions().get(0);
}
private Optional<Parameters.ParametersParameterComponent> getNotificationEventFocus(List<Resource> theResources) {
assertEquals("Parameters", theResources.get(0).getResourceType().name());
Parameters parameters = (Parameters) theResources.get(0);
Parameters.ParametersParameterComponent notificationEvent = parameters.getParameter("notification-event");
assertNotNull(notificationEvent);
return notificationEvent.getPart().stream()
.filter(part -> part.getName().equals("focus"))
.findFirst();
}
private void validateRequestParameters(Bundle.BundleEntryComponent thePatientEntry) {
assertNotNull(thePatientEntry.getRequest());
assertEquals("POST", thePatientEntry.getRequest().getMethod().name());
assertEquals("Patient", thePatientEntry.getRequest().getUrl());
}
}

View File

@ -60,6 +60,7 @@ import java.util.Map;
import java.util.stream.Collectors;
import static ca.uhn.fhir.util.HapiExtensions.EX_SEND_DELETE_MESSAGES;
import static java.util.Objects.nonNull;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
@ -305,8 +306,6 @@ public class SubscriptionCanonicalizer {
CanonicalTopicSubscription topicSubscription = retVal.getTopicSubscription();
topicSubscription.setTopic(getCriteria(theSubscription));
// WIP STR5 support other content types
topicSubscription.setContent(org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.FULLRESOURCE);
retVal.setEndpointUrl(channel.getEndpoint());
retVal.setChannelType(getChannelType(subscription));
@ -320,31 +319,37 @@ public class SubscriptionCanonicalizer {
}
if (channel.hasExtension(SubscriptionConstants.SUBSCRIPTION_TOPIC_CHANNEL_HEARTBEAT_PERIOD_URL)) {
org.hl7.fhir.r4.model.Extension timeoutExtension = channel.getExtensionByUrl(
org.hl7.fhir.r4.model.Extension channelHeartbeatPeriotUrlExtension = channel.getExtensionByUrl(
SubscriptionConstants.SUBSCRIPTION_TOPIC_CHANNEL_HEARTBEAT_PERIOD_URL);
topicSubscription.setHeartbeatPeriod(
Integer.valueOf(timeoutExtension.getValue().primitiveValue()));
topicSubscription.setHeartbeatPeriod(Integer.valueOf(
channelHeartbeatPeriotUrlExtension.getValue().primitiveValue()));
}
if (channel.hasExtension(SubscriptionConstants.SUBSCRIPTION_TOPIC_CHANNEL_TIMEOUT_URL)) {
org.hl7.fhir.r4.model.Extension timeoutExtension =
org.hl7.fhir.r4.model.Extension channelTimeoutUrlExtension =
channel.getExtensionByUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_CHANNEL_TIMEOUT_URL);
topicSubscription.setTimeout(
Integer.valueOf(timeoutExtension.getValue().primitiveValue()));
Integer.valueOf(channelTimeoutUrlExtension.getValue().primitiveValue()));
}
if (channel.hasExtension(SubscriptionConstants.SUBSCRIPTION_TOPIC_CHANNEL_MAX_COUNT)) {
org.hl7.fhir.r4.model.Extension timeoutExtension =
org.hl7.fhir.r4.model.Extension channelMaxCountExtension =
channel.getExtensionByUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_CHANNEL_MAX_COUNT);
topicSubscription.setMaxCount(
Integer.valueOf(timeoutExtension.getValue().primitiveValue()));
}
if (channel.getPayloadElement()
.hasExtension(SubscriptionConstants.SUBSCRIPTION_TOPIC_CHANNEL_PAYLOAD_CONTENT)) {
org.hl7.fhir.r4.model.Extension timeoutExtension = channel.getPayloadElement()
.getExtensionByUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_CHANNEL_PAYLOAD_CONTENT);
topicSubscription.setContent(org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.fromCode(
timeoutExtension.getValue().primitiveValue()));
Integer.valueOf(channelMaxCountExtension.getValue().primitiveValue()));
}
// setting full-resource PayloadContent if backport-payload-content is not provided
org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent payloadContent =
org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.FULLRESOURCE;
org.hl7.fhir.r4.model.Extension channelPayloadContentExtension = channel.getPayloadElement()
.getExtensionByUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_CHANNEL_PAYLOAD_CONTENT);
if (nonNull(channelPayloadContentExtension)) {
payloadContent = org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.fromCode(
channelPayloadContentExtension.getValue().primitiveValue());
}
topicSubscription.setContent(payloadContent);
} else {
retVal.setCriteriaString(getCriteria(theSubscription));
retVal.setEndpointUrl(channel.getEndpoint());
@ -423,13 +428,25 @@ public class SubscriptionCanonicalizer {
}
if (retVal.isTopicSubscription()) {
retVal.getTopicSubscription().setTopic(getCriteria(theSubscription));
CanonicalTopicSubscription topicSubscription = retVal.getTopicSubscription();
topicSubscription.setTopic(getCriteria(theSubscription));
// WIP STR5 support other content types
retVal.getTopicSubscription()
.setContent(org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.FULLRESOURCE);
retVal.setEndpointUrl(channel.getEndpoint());
retVal.setChannelType(getChannelType(subscription));
// setting full-resource PayloadContent if backport-payload-content is not provided
org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent payloadContent =
org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.FULLRESOURCE;
org.hl7.fhir.r4b.model.Extension channelPayloadContentExtension = channel.getPayloadElement()
.getExtensionByUrl(SubscriptionConstants.SUBSCRIPTION_TOPIC_CHANNEL_PAYLOAD_CONTENT);
if (nonNull(channelPayloadContentExtension)) {
payloadContent = org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.fromCode(
channelPayloadContentExtension.getValue().primitiveValue());
}
topicSubscription.setContent(payloadContent);
} else {
retVal.setCriteriaString(getCriteria(theSubscription));
retVal.setEndpointUrl(channel.getEndpoint());

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType;
import ca.uhn.fhir.jpa.subscription.model.CanonicalTopicSubscriptionFilter;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.primitive.BooleanDt;
import ca.uhn.fhir.subscription.SubscriptionConstants;
import ca.uhn.fhir.subscription.SubscriptionTestDataHelper;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Extension;
@ -14,6 +15,8 @@ import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.Enumerations;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static ca.uhn.fhir.rest.api.Constants.CT_FHIR_JSON_NEW;
import static ca.uhn.fhir.util.HapiExtensions.EX_SEND_DELETE_MESSAGES;
@ -83,16 +86,12 @@ class SubscriptionCanonicalizerTest {
assertTrue(canonicalize.getSendDeleteMessages());
}
@Test
public void testR5() {
// setup
SubscriptionCanonicalizer r5Canonicalizer = new SubscriptionCanonicalizer(FhirContext.forR5Cached());
private org.hl7.fhir.r5.model.Subscription buildR5Subscription(org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent thePayloadContent) {
org.hl7.fhir.r5.model.Subscription subscription = new org.hl7.fhir.r5.model.Subscription();
subscription.setStatus(Enumerations.SubscriptionStatusCodes.ACTIVE);
subscription.setContentType(CT_FHIR_JSON_NEW);
// WIP STR5 support different content types
subscription.setContent(org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.FULLRESOURCE);
subscription.setContent(thePayloadContent);
subscription.setEndpoint("http://foo");
subscription.setTopic(SubscriptionTestDataHelper.TEST_TOPIC);
Coding channelType = new Coding().setSystem("http://terminology.hl7.org/CodeSystem/subscription-channel-type").setCode("rest-hook");
@ -102,13 +101,25 @@ class SubscriptionCanonicalizerTest {
subscription.setHeartbeatPeriod(123);
subscription.setMaxCount(456);
return subscription;
}
@ParameterizedTest
@ValueSource(strings = {"full-resource", "id-only", "empty"})
public void testR5Canonicalize_returnsCorrectCanonicalSubscription(String thePayloadContent) {
// setup
SubscriptionCanonicalizer r5Canonicalizer = new SubscriptionCanonicalizer(FhirContext.forR5Cached());
org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent payloadContent =
org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.fromCode(thePayloadContent);
org.hl7.fhir.r5.model.Subscription subscription = buildR5Subscription(payloadContent);
// execute
CanonicalSubscription canonical = r5Canonicalizer.canonicalize(subscription);
// verify
assertEquals(Subscription.SubscriptionStatus.ACTIVE, canonical.getStatus());
assertEquals(CT_FHIR_JSON_NEW, canonical.getContentType());
assertEquals(org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.FULLRESOURCE, canonical.getContent());
assertEquals(payloadContent, canonical.getContent());
assertEquals("http://foo", canonical.getEndpointUrl());
assertEquals(SubscriptionTestDataHelper.TEST_TOPIC, canonical.getTopic());
assertEquals(CanonicalSubscriptionChannelType.RESTHOOK, canonical.getChannelType());
@ -131,37 +142,72 @@ class SubscriptionCanonicalizerTest {
assertEquals(456, canonical.getMaxCount());
}
@Test
void testR4Backport() {
@ParameterizedTest
@ValueSource(strings = {"full-resource", "id-only", "empty"})
void testR4BCanonicalize_returnsCorrectCanonicalSubscription(String thePayloadContent) {
// Example drawn from http://build.fhir.org/ig/HL7/fhir-subscription-backport-ig/Subscription-subscription-zulip.json.html
// setup
SubscriptionCanonicalizer r4bCanonicalizer = new SubscriptionCanonicalizer(FhirContext.forR4BCached());
org.hl7.fhir.r4b.model.Subscription subscription = buildR4BSubscription(thePayloadContent);
// execute
CanonicalSubscription canonical = r4bCanonicalizer.canonicalize(subscription);
// verify
assertEquals(Subscription.SubscriptionStatus.ACTIVE, canonical.getStatus());
verifyStandardSubscriptionParameters(canonical);
verifyChannelParameters(canonical, thePayloadContent);
}
private org.hl7.fhir.r4b.model.Subscription buildR4BSubscription(String thePayloadContent) {
org.hl7.fhir.r4b.model.Subscription subscription = new org.hl7.fhir.r4b.model.Subscription();
subscription.setId("testId");
subscription.getMeta().addTag("http://a", "b", "c");
subscription.getMeta().addTag("http://d", "e", "f");
subscription.setStatus(org.hl7.fhir.r4b.model.Enumerations.SubscriptionStatus.ACTIVE);
subscription.getChannel().setPayload(CT_FHIR_JSON_NEW);
subscription.getChannel().setType(org.hl7.fhir.r4b.model.Subscription.SubscriptionChannelType.RESTHOOK);
subscription.getChannel().setEndpoint(SubscriptionTestDataHelper.TEST_ENDPOINT);
subscription.getMeta().addProfile(SubscriptionConstants.SUBSCRIPTION_TOPIC_PROFILE_URL);
subscription.setCriteria(SubscriptionTestDataHelper.TEST_TOPIC);
subscription.getChannel().setPayload(CT_FHIR_JSON_NEW);
subscription.getChannel().addHeader(SubscriptionTestDataHelper.TEST_HEADER1);
subscription.getChannel().addHeader(SubscriptionTestDataHelper.TEST_HEADER2);
subscription.setStatus(org.hl7.fhir.r4b.model.Enumerations.SubscriptionStatus.ACTIVE);
subscription
.getChannel()
.getPayloadElement()
.addExtension(
SubscriptionConstants.SUBSCRIPTION_TOPIC_CHANNEL_PAYLOAD_CONTENT,
new org.hl7.fhir.r4b.model.CodeType(thePayloadContent));
return subscription;
}
@ParameterizedTest
@ValueSource(strings = {"full-resource", "id-only", "empty"})
void testR4canonicalize_withBackPortedSubscription_returnsCorrectCanonicalSubscription(String thePayloadContent) {
// Example drawn from http://build.fhir.org/ig/HL7/fhir-subscription-backport-ig/Subscription-subscription-zulip.json.html
// setup
SubscriptionCanonicalizer r4Canonicalizer = new SubscriptionCanonicalizer(FhirContext.forR4Cached());
// execute
CanonicalSubscription canonical = r4Canonicalizer.canonicalize(SubscriptionTestDataHelper.buildR4TopicSubscription());
Subscription subscription = SubscriptionTestDataHelper.buildR4TopicSubscriptionWithContent(thePayloadContent);
CanonicalSubscription canonical = r4Canonicalizer.canonicalize(subscription);
// verify
// Standard R4 stuff
assertEquals(2, canonical.getTags().size());
assertEquals("b", canonical.getTags().get("http://a"));
assertEquals("e", canonical.getTags().get("http://d"));
assertEquals("testId", canonical.getIdPart());
assertEquals("testId", canonical.getIdElementString());
assertEquals(SubscriptionTestDataHelper.TEST_ENDPOINT, canonical.getEndpointUrl());
assertEquals(CT_FHIR_JSON_NEW, canonical.getContentType());
assertThat(canonical.getHeaders(), hasSize(2));
assertEquals(SubscriptionTestDataHelper.TEST_HEADER1, canonical.getHeaders().get(0));
assertEquals(SubscriptionTestDataHelper.TEST_HEADER2, canonical.getHeaders().get(1));
verifyStandardSubscriptionParameters(canonical);
assertEquals(Subscription.SubscriptionStatus.ACTIVE, canonical.getStatus());
verifyChannelParameters(canonical, thePayloadContent);
assertEquals(CT_FHIR_JSON_NEW, canonical.getContentType());
assertEquals(org.hl7.fhir.r5.model.Subscription.SubscriptionPayloadContent.FULLRESOURCE, canonical.getContent());
assertEquals(SubscriptionTestDataHelper.TEST_ENDPOINT, canonical.getEndpointUrl());
assertEquals(SubscriptionTestDataHelper.TEST_TOPIC, canonical.getTopic());
assertEquals(CanonicalSubscriptionChannelType.RESTHOOK, canonical.getChannelType());
assertThat(canonical.getFilters(), hasSize(2));
CanonicalTopicSubscriptionFilter filter1 = canonical.getFilters().get(0);
@ -183,6 +229,26 @@ class SubscriptionCanonicalizerTest {
assertEquals(20, canonical.getMaxCount());
}
private void verifyChannelParameters(CanonicalSubscription theCanonicalSubscriptions, String thePayloadContent) {
assertThat(theCanonicalSubscriptions.getHeaders(), hasSize(2));
assertEquals(SubscriptionTestDataHelper.TEST_HEADER1, theCanonicalSubscriptions.getHeaders().get(0));
assertEquals(SubscriptionTestDataHelper.TEST_HEADER2, theCanonicalSubscriptions.getHeaders().get(1));
assertEquals(CT_FHIR_JSON_NEW, theCanonicalSubscriptions.getContentType());
assertEquals(thePayloadContent, theCanonicalSubscriptions.getContent().toCode());
assertEquals(SubscriptionTestDataHelper.TEST_ENDPOINT, theCanonicalSubscriptions.getEndpointUrl());
assertEquals(SubscriptionTestDataHelper.TEST_TOPIC, theCanonicalSubscriptions.getTopic());
assertEquals(CanonicalSubscriptionChannelType.RESTHOOK, theCanonicalSubscriptions.getChannelType());
}
private void verifyStandardSubscriptionParameters(CanonicalSubscription theCanonicalSubscription) {
assertEquals(2, theCanonicalSubscription.getTags().size());
assertEquals("b", theCanonicalSubscription.getTags().get("http://a"));
assertEquals("e", theCanonicalSubscription.getTags().get("http://d"));
assertEquals("testId", theCanonicalSubscription.getIdPart());
assertEquals("testId", theCanonicalSubscription.getIdElementString());
}
@NotNull
private static org.hl7.fhir.r5.model.Subscription.SubscriptionFilterByComponent buildFilter(String theResourceType, String theParam, String theValue) {
org.hl7.fhir.r5.model.Subscription.SubscriptionFilterByComponent filter = new org.hl7.fhir.r5.model.Subscription.SubscriptionFilterByComponent();

View File

@ -36,6 +36,10 @@ public class SubscriptionTestDataHelper {
public static final String TEST_HEADER2 = "X-Bar: BAR";
public static Subscription buildR4TopicSubscription() {
return buildR4TopicSubscriptionWithContent("full-resource");
}
public static Subscription buildR4TopicSubscriptionWithContent(String theChannelPayloadContent) {
Subscription subscription = new Subscription();
// Standard R4 stuff
@ -75,7 +79,7 @@ public class SubscriptionTestDataHelper {
.getPayloadElement()
.addExtension(
SubscriptionConstants.SUBSCRIPTION_TOPIC_CHANNEL_PAYLOAD_CONTENT,
new CodeType("full-resource"));
new CodeType(theChannelPayloadContent));
return subscription;
}