Merge pull request #1210 from jamesagnew/ja_allow_multiple_canonical_subs_extensions

Allow canonical subscription to hold multiple extensions with the same
This commit is contained in:
James Agnew 2019-02-16 17:11:51 -05:00 committed by GitHub
commit fb5d94383b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 122 additions and 89 deletions

View File

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

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.searchparam.registry;
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View File

@ -32,6 +32,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Subscription; import org.hl7.fhir.r4.model.Subscription;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable; import java.io.Serializable;
import java.util.*; import java.util.*;
@ -64,7 +65,7 @@ public class CanonicalSubscription implements Serializable, Cloneable {
@JsonProperty("restHookDetails") @JsonProperty("restHookDetails")
private RestHookDetails myRestHookDetails; private RestHookDetails myRestHookDetails;
@JsonProperty("extensions") @JsonProperty("extensions")
private Map<String, String> myChannelExtensions; private Map<String, List<String>> myChannelExtensions;
/** /**
* Constructor * Constructor
@ -134,15 +135,32 @@ public class CanonicalSubscription implements Serializable, Cloneable {
} }
} }
public String getChannelExtension(String url) { public String getChannelExtension(String theUrl) {
return myChannelExtensions.get(url); 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<>(); myChannelExtensions = new HashMap<>();
for (String url : theChannelExtensions.keySet()) { for (String url : theChannelExtensions.keySet()) {
if (isNotBlank(url) && isNotBlank(theChannelExtensions.get(url))) { List<String> values = theChannelExtensions.get(url);
myChannelExtensions.put(url, 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.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType;
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy; 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.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
@ -40,11 +38,14 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.HashMap; import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; 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 @Service
public class SubscriptionCanonicalizer<S extends IBaseResource> { public class SubscriptionCanonicalizer<S extends IBaseResource> {
@ -61,6 +62,8 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
return canonicalizeDstu3(theSubscription); return canonicalizeDstu3(theSubscription);
case R4: case R4:
return canonicalizeR4(theSubscription); return canonicalizeR4(theSubscription);
case DSTU2_HL7ORG:
case DSTU2_1:
default: default:
throw new ConfigurationException("Subscription not supported for version: " + myFhirContext.getVersion().getVersion()); 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.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint()); retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader()); retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setChannelExtensions(convertChannelExtensionsDstu2(subscription)); retVal.setChannelExtensions(extractExtension(subscription));
retVal.setIdElement(subscription.getIdElement()); retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload()); retVal.setPayloadString(subscription.getChannel().getPayload());
} catch (FHIRException theE) { } catch (FHIRException theE) {
@ -95,11 +98,11 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
retVal.setCriteriaString(subscription.getCriteria()); retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint()); retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader()); retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setChannelExtensions(convertChannelExtensionsDstu3(subscription)); retVal.setChannelExtensions(extractExtension(subscription));
retVal.setIdElement(subscription.getIdElement()); retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload()); retVal.setPayloadString(subscription.getChannel().getPayload());
if (retVal.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) { if (retVal.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) {
String from; String from;
String subjectTemplate; String subjectTemplate;
@ -133,85 +136,45 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
return retVal; return retVal;
} }
private Map<String, String> convertChannelExtensionsDstu2(ca.uhn.fhir.model.dstu2.resource.Subscription theSubscription) { private @Nonnull
Map<String, String> retval = new HashMap<>(); Map<String, List<String>> extractExtension(IBaseResource theSubscription) {
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) {
try { try {
switch (theSubscription.getStructureFhirVersionEnum()) { switch (theSubscription.getStructureFhirVersionEnum()) {
case DSTU2: { case DSTU2: {
ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription; ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription;
List<ExtensionDt> extensions = subscription.getChannel().getUndeclaredExtensionsByUrl(theUrl); return subscription
if (extensions.size() == 0) { .getChannel()
return null; .getUndeclaredExtensions()
} .stream()
if (extensions.size() > 1) { .collect(Collectors.groupingBy(t -> t.getUrl(), mapping(t -> t.getValueAsPrimitive().getValueAsString(), toList())));
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();
} }
case DSTU3: { case DSTU3: {
org.hl7.fhir.dstu3.model.Subscription subscription = (org.hl7.fhir.dstu3.model.Subscription) theSubscription; 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: { case R4: {
org.hl7.fhir.r4.model.Subscription subscription = (org.hl7.fhir.r4.model.Subscription) theSubscription; 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_HL7ORG:
case DSTU2_1: case DSTU2_1:
default: { 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; break;
} }
} }
} catch (FHIRException theE) { } 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) { private CanonicalSubscription canonicalizeR4(IBaseResource theSubscription) {
@ -223,7 +186,7 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
retVal.setCriteriaString(subscription.getCriteria()); retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint()); retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader()); retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setChannelExtensions(convertChannelExtensionsR4(subscription)); retVal.setChannelExtensions(extractExtension(subscription));
retVal.setIdElement(subscription.getIdElement()); retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload()); retVal.setPayloadString(subscription.getChannel().getPayload());
@ -267,18 +230,20 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
public String getCriteria(IBaseResource theSubscription) { public String getCriteria(IBaseResource theSubscription) {
switch (myFhirContext.getVersion().getVersion()) { switch (myFhirContext.getVersion().getVersion()) {
case DSTU2: case DSTU2:
return ((ca.uhn.fhir.model.dstu2.resource.Subscription)theSubscription).getCriteria(); return ((ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription).getCriteria();
case DSTU3: case DSTU3:
return ((org.hl7.fhir.dstu3.model.Subscription)theSubscription).getCriteria(); return ((org.hl7.fhir.dstu3.model.Subscription) theSubscription).getCriteria();
case R4: 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: default:
throw new ConfigurationException("Subscription not supported for version: " + myFhirContext.getVersion().getVersion()); 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(); IBaseMetaType meta = theSubscription.getMeta();
String value = theStrategy.toString(); String value = theStrategy.toString();
String display; String display;
@ -288,7 +253,7 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
} else if (theStrategy == SubscriptionMatchingStrategy.IN_MEMORY) { } else if (theStrategy == SubscriptionMatchingStrategy.IN_MEMORY) {
display = "In-memory"; display = "In-memory";
} else { } 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); 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

@ -4,6 +4,7 @@ package ca.uhn.fhir.jpa.subscription.module.cache;
import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test; import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test;
import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.dstu3.model.Subscription;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -16,8 +17,13 @@ public class SubscriptionRegistryTest extends BaseSubscriptionDstu3Test {
@Autowired @Autowired
SubscriptionRegistry mySubscriptionRegistry; SubscriptionRegistry mySubscriptionRegistry;
@Before
public void clearRegistryBefore() {
mySubscriptionRegistry.unregisterAllSubscriptions();
}
@After @After
public void clearRegistry() { public void clearRegistryAfter() {
mySubscriptionRegistry.unregisterAllSubscriptions(); mySubscriptionRegistry.unregisterAllSubscriptions();
} }

View File

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