Allow canonical subscription to hold multiple extensions with the same

URL
This commit is contained in:
James Agnew 2019-02-16 15:28:39 -05:00
parent 87ed00a002
commit c6fcb4fcbf
5 changed files with 115 additions and 88 deletions

View File

@ -198,7 +198,7 @@ public class SubscriptionActivatingInterceptor {
String criteria = mySubscriptionCanonicalizer.getCriteria(theResource);
try {
SubscriptionMatchingStrategy strategy = mySubscriptionStrategyEvaluator.determineStrategy(criteria);
mySubscriptionCanonicalizer.setMatchingStrategyTag(myFhirContext, theResource, strategy);
mySubscriptionCanonicalizer.setMatchingStrategyTag(theResource, strategy);
} catch (InvalidRequestException | DataFormatException e) {
throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + criteria + " " + e.getMessage());
}

View File

@ -32,6 +32,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Subscription;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.util.*;
@ -64,7 +65,7 @@ public class CanonicalSubscription implements Serializable, Cloneable {
@JsonProperty("restHookDetails")
private RestHookDetails myRestHookDetails;
@JsonProperty("extensions")
private Map<String, String> myChannelExtensions;
private Map<String, List<String>> myChannelExtensions;
/**
* Constructor
@ -134,15 +135,32 @@ public class CanonicalSubscription implements Serializable, Cloneable {
}
}
public String getChannelExtension(String url) {
return myChannelExtensions.get(url);
public String getChannelExtension(String theUrl) {
String retVal = null;
List<String> strings = myChannelExtensions.get(theUrl);
if (strings != null && strings.isEmpty()==false) {
retVal = strings.get(0);
}
return retVal;
}
public void setChannelExtensions(Map<String, String> theChannelExtensions) {
@Nonnull
public List<String> getChannelExtensions(String theUrl) {
List<String> retVal = myChannelExtensions.get(theUrl);
if (retVal == null) {
retVal = Collections.emptyList();
} else {
retVal = Collections.unmodifiableList(retVal);
}
return retVal;
}
public void setChannelExtensions(Map<String, List<String>> theChannelExtensions) {
myChannelExtensions = new HashMap<>();
for (String url : theChannelExtensions.keySet()) {
if (isNotBlank(url) && isNotBlank(theChannelExtensions.get(url))) {
myChannelExtensions.put(url, theChannelExtensions.get(url));
List<String> values = theChannelExtensions.get(url);
if (isNotBlank(url) && values != null) {
myChannelExtensions.put(url, values);
}
}
}

View File

@ -25,8 +25,6 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType;
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.hl7.fhir.exceptions.FHIRException;
@ -40,11 +38,14 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
@Service
public class SubscriptionCanonicalizer<S extends IBaseResource> {
@ -61,6 +62,8 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
return canonicalizeDstu3(theSubscription);
case R4:
return canonicalizeR4(theSubscription);
case DSTU2_HL7ORG:
case DSTU2_1:
default:
throw new ConfigurationException("Subscription not supported for version: " + myFhirContext.getVersion().getVersion());
}
@ -76,7 +79,7 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setChannelExtensions(convertChannelExtensionsDstu2(subscription));
retVal.setChannelExtensions(extractExtension(subscription));
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
} catch (FHIRException theE) {
@ -95,7 +98,7 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setChannelExtensions(convertChannelExtensionsDstu3(subscription));
retVal.setChannelExtensions(extractExtension(subscription));
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
@ -133,85 +136,45 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
return retVal;
}
private Map<String, String> convertChannelExtensionsDstu2(ca.uhn.fhir.model.dstu2.resource.Subscription theSubscription) {
Map<String, String> retval = new HashMap<>();
for (ExtensionDt extension : theSubscription.getChannel().getUndeclaredExtensions()) {
String url = extension.getUrl();
if (isNotBlank(url)) {
String value = extractExtension(theSubscription, url);
if (isNotBlank(value)) {
retval.put(url, value);
}
}
}
return retval;
}
private Map<String, String> convertChannelExtensionsDstu3(org.hl7.fhir.dstu3.model.Subscription theSubscription) {
Map<String, String> retval = new HashMap<>();
for (org.hl7.fhir.dstu3.model.Extension extension : theSubscription.getChannel().getExtension()) {
String url = extension.getUrl();
if (isNotBlank(url)) {
String value = extractExtension(theSubscription, url);
if (isNotBlank(value)) {
retval.put(url, value);
}
}
}
return retval;
}
private Map<String, String> convertChannelExtensionsR4(org.hl7.fhir.r4.model.Subscription theSubscription) {
Map<String, String> retval = new HashMap<>();
for (org.hl7.fhir.r4.model.Extension extension : theSubscription.getChannel().getExtension()) {
String url = extension.getUrl();
if (isNotBlank(url)) {
String value = extractExtension(theSubscription, url);
if (isNotBlank(value)) {
retval.put(url, value);
}
}
}
return retval;
}
private String extractExtension(IBaseResource theSubscription, String theUrl) {
private @Nonnull
Map<String, List<String>> extractExtension(IBaseResource theSubscription) {
try {
switch (theSubscription.getStructureFhirVersionEnum()) {
case DSTU2: {
ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription;
List<ExtensionDt> extensions = subscription.getChannel().getUndeclaredExtensionsByUrl(theUrl);
if (extensions.size() == 0) {
return null;
}
if (extensions.size() > 1) {
throw new FHIRException("Multiple matching extensions found");
}
if (!(extensions.get(0).getValue() instanceof IPrimitiveDatatype)) {
throw new FHIRException("Extension could not be converted to a string");
}
return ((IPrimitiveDatatype<?>) extensions.get(0).getValue()).getValueAsString();
return subscription
.getChannel()
.getUndeclaredExtensions()
.stream()
.collect(Collectors.groupingBy(t -> t.getUrl(), mapping(t -> t.getValueAsPrimitive().getValueAsString(), toList())));
}
case DSTU3: {
org.hl7.fhir.dstu3.model.Subscription subscription = (org.hl7.fhir.dstu3.model.Subscription) theSubscription;
return subscription.getChannel().getExtensionString(theUrl);
return subscription
.getChannel()
.getExtension()
.stream()
.collect(Collectors.groupingBy(t -> t.getUrl(), mapping(t -> t.getValueAsPrimitive().getValueAsString(), toList())));
}
case R4: {
org.hl7.fhir.r4.model.Subscription subscription = (org.hl7.fhir.r4.model.Subscription) theSubscription;
return subscription.getChannel().getExtensionString(theUrl);
return subscription
.getChannel()
.getExtension()
.stream()
.collect(Collectors.groupingBy(t -> t.getUrl(), mapping(t -> t.getValueAsPrimitive().getValueAsString(), toList())));
}
case DSTU2_HL7ORG:
case DSTU2_1:
default: {
ourLog.error("Failed to extract extension with URL {} from subscription {}", theUrl, theSubscription.getIdElement().toUnqualified().getValue());
ourLog.error("Failed to extract extension from subscription {}", theSubscription.getIdElement().toUnqualified().getValue());
break;
}
}
} catch (FHIRException theE) {
ourLog.error("Failed to extract extension with URL {} from subscription {}", theUrl, theSubscription.getIdElement().toUnqualified().getValue(), theE);
ourLog.error("Failed to extract extension from subscription {}", theSubscription.getIdElement().toUnqualified().getValue(), theE);
}
return null;
return Collections.emptyMap();
}
private CanonicalSubscription canonicalizeR4(IBaseResource theSubscription) {
@ -223,7 +186,7 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setChannelExtensions(convertChannelExtensionsR4(subscription));
retVal.setChannelExtensions(extractExtension(subscription));
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
@ -267,18 +230,20 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
public String getCriteria(IBaseResource theSubscription) {
switch (myFhirContext.getVersion().getVersion()) {
case DSTU2:
return ((ca.uhn.fhir.model.dstu2.resource.Subscription)theSubscription).getCriteria();
return ((ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription).getCriteria();
case DSTU3:
return ((org.hl7.fhir.dstu3.model.Subscription)theSubscription).getCriteria();
return ((org.hl7.fhir.dstu3.model.Subscription) theSubscription).getCriteria();
case R4:
return ((org.hl7.fhir.r4.model.Subscription)theSubscription).getCriteria();
return ((org.hl7.fhir.r4.model.Subscription) theSubscription).getCriteria();
case DSTU2_1:
case DSTU2_HL7ORG:
default:
throw new ConfigurationException("Subscription not supported for version: " + myFhirContext.getVersion().getVersion());
}
}
public void setMatchingStrategyTag(FhirContext theFhirContext, IBaseResource theSubscription, SubscriptionMatchingStrategy theStrategy) {
public void setMatchingStrategyTag(IBaseResource theSubscription, SubscriptionMatchingStrategy theStrategy) {
IBaseMetaType meta = theSubscription.getMeta();
String value = theStrategy.toString();
String display;
@ -288,7 +253,7 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
} else if (theStrategy == SubscriptionMatchingStrategy.IN_MEMORY) {
display = "In-memory";
} else {
throw new IllegalStateException("Unknown " + SubscriptionMatchingStrategy.class.getSimpleName() + ": "+theStrategy);
throw new IllegalStateException("Unknown " + SubscriptionMatchingStrategy.class.getSimpleName() + ": " + theStrategy);
}
meta.addTag().setSystem(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY).setCode(value).setDisplay(display);
}

View File

@ -0,0 +1,44 @@
package ca.uhn.fhir.jpa.subscription.module;
import org.assertj.core.util.Lists;
import org.hamcrest.Matchers;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.contains;
public class CanonicalSubscriptionTest {
@Test
public void testGetChannelExtension() {
HashMap<String, List<String>> inMap = new HashMap<>();
inMap.put("key1", Lists.newArrayList("VALUE1"));
inMap.put("key2", Lists.newArrayList("VALUE2a", "VALUE2b"));
CanonicalSubscription s = new CanonicalSubscription();
s.setChannelExtensions(inMap);
assertThat(s.getChannelExtension("key1"), Matchers.equalTo("VALUE1"));
assertThat(s.getChannelExtension("key2"), Matchers.equalTo("VALUE2a"));
assertThat(s.getChannelExtension("key3"), Matchers.nullValue());
}
@Test
public void testGetChannelExtensions() {
HashMap<String, List<String>> inMap = new HashMap<>();
inMap.put("key1", Lists.newArrayList("VALUE1"));
inMap.put("key2", Lists.newArrayList("VALUE2a", "VALUE2b"));
CanonicalSubscription s = new CanonicalSubscription();
s.setChannelExtensions(inMap);
assertThat(s.getChannelExtensions("key1"), Matchers.contains("VALUE1"));
assertThat(s.getChannelExtensions("key2"), Matchers.contains("VALUE2a", "VALUE2b"));
assertThat(s.getChannelExtensions("key3"), Matchers.empty());
}
}

View File

@ -186,7 +186,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
}
@Override
public Class<? extends IBaseResource> getResourceType() {
public Class<T> getResourceType() {
return myResourceType;
}
@ -211,7 +211,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
}
@Read(version = true)
public IBaseResource read(@IdParam IIdType theId) {
public T read(@IdParam IIdType theId) {
TreeMap<Long, T> versions = myIdToVersionToResourceMap.get(theId.getIdPart());
if (versions == null || versions.isEmpty()) {
throw new ResourceNotFoundException(theId);
@ -240,8 +240,8 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
}
@Search
public List<IBaseResource> searchAll() {
List<IBaseResource> retVal = new ArrayList<>();
public List<T> searchAll() {
List<T> retVal = new ArrayList<>();
for (TreeMap<Long, T> next : myIdToVersionToResourceMap.values()) {
if (next.isEmpty() == false) {
@ -255,10 +255,10 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
}
@Search
public List<IBaseResource> searchById(
public List<T> searchById(
@RequiredParam(name = "_id") TokenAndListParam theIds) {
List<IBaseResource> retVal = new ArrayList<>();
List<T> retVal = new ArrayList<>();
for (TreeMap<Long, T> next : myIdToVersionToResourceMap.values()) {
if (next.isEmpty() == false) {