Support multiple EID systems for MDM. (#3178)
* Add implementation, changelog, docs, and tests * Fix potential NPE * Use constant instead of *
This commit is contained in:
parent
3c2b4bb397
commit
41ef7cf961
|
@ -80,7 +80,6 @@ public enum VersionEnum {
|
|||
V5_5_3,
|
||||
V5_6_0,
|
||||
V5_7_0
|
||||
|
||||
;
|
||||
|
||||
public static VersionEnum latestVersion() {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: add
|
||||
issue: 3158
|
||||
title: "Add the concept of `messageKey` to the BaseResourceMessage class. This is to support brokers which permit key-based
|
||||
partitioning."
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: change
|
||||
issue: 3158
|
||||
title: "Previously in configuring MDM, you were only allowed to set a single `eidSystem` which was global regardless of how many resource types you were performing
|
||||
MDM on. This has been changed. That field is now deprecated, and a new field called `eidSystems` (a json object) should be used instead. This will allow you to have granular
|
||||
control over which resource types use which EIDs. See the [documentation](/hapi-fhir/docs/server_jpa_mdm/mdm_rules.html) of `eidSystems` for more information."
|
|
@ -99,7 +99,11 @@ Here is an example of a full HAPI MDM rules json document:
|
|||
"lastname-jaro,phone,birthday": "POSSIBLE_MATCH",
|
||||
"firstname-jaro,phone,birthday": "POSSIBLE_MATCH",
|
||||
"org-name": "MATCH"
|
||||
}
|
||||
},
|
||||
"eidSystems": {
|
||||
"Organization": "https://hapifhir.org/identifier/naming/business-number",
|
||||
"Practitioner": "https://hapifhir.org/identifier/naming/license-number"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -483,6 +487,13 @@ These entries convert combinations of successful matchFields into an MDM Match R
|
|||
}
|
||||
```
|
||||
|
||||
### eidSystem
|
||||
### eidSystems
|
||||
|
||||
The external EID systems that the HAPI MDM system can expect to see on incoming resources. These are defined on a per-resource basis. Alternatively, you may use `*` to indicate
|
||||
that an EID is valid for all managed resource types. The values must be valid URIs, and the keys must be valid resource types, or `*`.
|
||||
See [MDM EID](/hapi-fhir/docs/server_jpa_mdm/mdm_eid.html) for details on how EIDs are managed by HAPI MDM.
|
||||
|
||||
<p class="helpInfoCalloutBox">
|
||||
Note that this field used to be called `eidSystem`. While that field is deprecated, it will continue to work. In the background, it effectively sets the eid for resource type `*`.
|
||||
</p>
|
||||
|
||||
The external EID system that the HAPI MDM system should expect to see on incoming Patient resources. Must be a valid URI. See [MDM EID](/hapi-fhir/docs/server_jpa_mdm/mdm_eid.html) for details on how EIDs are managed by HAPI MDM.
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package ca.uhn.fhir.jpa.mdm.broker;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.api.ISubscriptionMessageKeySvc;
|
||||
import ca.uhn.fhir.mdm.model.CanonicalEID;
|
||||
import ca.uhn.fhir.mdm.util.EIDHelper;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class MdmMessageKeySvc implements ISubscriptionMessageKeySvc {
|
||||
@Autowired
|
||||
private EIDHelper myEIDHelper;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getMessageKeyOrNull(IBaseResource theTargetResource) {
|
||||
List<CanonicalEID> eidList = myEIDHelper.getExternalEid(theTargetResource);
|
||||
if (eidList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return eidList.get(0).getValue();
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import ca.uhn.fhir.jpa.batch.mdm.MdmBatchJobSubmitterFactoryImpl;
|
|||
import ca.uhn.fhir.jpa.dao.mdm.MdmLinkDeleteSvc;
|
||||
import ca.uhn.fhir.jpa.interceptor.MdmSearchExpandingInterceptor;
|
||||
import ca.uhn.fhir.jpa.mdm.broker.MdmMessageHandler;
|
||||
import ca.uhn.fhir.jpa.mdm.broker.MdmMessageKeySvc;
|
||||
import ca.uhn.fhir.jpa.mdm.broker.MdmQueueConsumerLoader;
|
||||
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.mdm.dao.MdmLinkFactory;
|
||||
|
@ -104,6 +105,10 @@ public class MdmConsumerConfig {
|
|||
return new MdmMessageHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
MdmMessageKeySvc mdmMessageKeySvc() {
|
||||
return new MdmMessageKeySvc();
|
||||
}
|
||||
@Bean
|
||||
MdmMatchLinkSvc mdmMatchLinkSvc() {
|
||||
return new MdmMatchLinkSvc();
|
||||
|
|
|
@ -77,7 +77,7 @@ public class MdmResourceDaoSvc {
|
|||
|
||||
//TODO GGG MDM address this
|
||||
public Optional<IAnyResource> searchGoldenResourceByEID(String theEid, String theResourceType) {
|
||||
SearchParameterMap map = buildEidSearchParameterMap(theEid);
|
||||
SearchParameterMap map = buildEidSearchParameterMap(theEid, theResourceType);
|
||||
|
||||
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourceType);
|
||||
IBundleProvider search = resourceDao.search(map);
|
||||
|
@ -101,10 +101,10 @@ public class MdmResourceDaoSvc {
|
|||
}
|
||||
|
||||
@Nonnull
|
||||
private SearchParameterMap buildEidSearchParameterMap(String theTheEid) {
|
||||
private SearchParameterMap buildEidSearchParameterMap(String theEid, String theResourceType) {
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
map.add("identifier", new TokenParam(myMdmSettings.getMdmRules().getEnterpriseEIDSystem(), theTheEid));
|
||||
map.add("identifier", new TokenParam(myMdmSettings.getMdmRules().getEnterpriseEIDSystemForResourceType(theResourceType), theEid));
|
||||
map.add("_tag", new TokenParam(MdmConstants.SYSTEM_GOLDEN_RECORD_STATUS, MdmConstants.CODE_GOLDEN_RECORD));
|
||||
return map;
|
||||
}
|
||||
|
|
|
@ -327,12 +327,12 @@ abstract public class BaseMdmR4Test extends BaseJpaR4Test {
|
|||
}
|
||||
|
||||
protected Patient addExternalEID(Patient thePatient, String theEID) {
|
||||
thePatient.addIdentifier().setSystem(myMdmSettings.getMdmRules().getEnterpriseEIDSystem()).setValue(theEID);
|
||||
thePatient.addIdentifier().setSystem(myMdmSettings.getMdmRules().getEnterpriseEIDSystemForResourceType("Patient")).setValue(theEID);
|
||||
return thePatient;
|
||||
}
|
||||
|
||||
protected Patient clearExternalEIDs(Patient thePatient) {
|
||||
thePatient.getIdentifier().removeIf(theIdentifier -> theIdentifier.getSystem().equalsIgnoreCase(myMdmSettings.getMdmRules().getEnterpriseEIDSystem()));
|
||||
thePatient.getIdentifier().removeIf(theIdentifier -> theIdentifier.getSystem().equalsIgnoreCase(myMdmSettings.getMdmRules().getEnterpriseEIDSystemForResourceType("Patient")));
|
||||
return thePatient;
|
||||
}
|
||||
|
||||
|
|
|
@ -71,11 +71,11 @@ public class MdmMatchLinkSvcMultipleEidModeTest extends BaseMdmR4Test {
|
|||
|
||||
//The collision should have added a new identifier with the external system.
|
||||
Identifier secondIdentifier = identifier.get(1);
|
||||
assertThat(secondIdentifier.getSystem(), is(equalTo(myMdmSettings.getMdmRules().getEnterpriseEIDSystem())));
|
||||
assertThat(secondIdentifier.getSystem(), is(equalTo(myMdmSettings.getMdmRules().getEnterpriseEIDSystemForResourceType("Patient"))));
|
||||
assertThat(secondIdentifier.getValue(), is(equalTo("12345")));
|
||||
|
||||
Identifier thirdIdentifier = identifier.get(2);
|
||||
assertThat(thirdIdentifier.getSystem(), is(equalTo(myMdmSettings.getMdmRules().getEnterpriseEIDSystem())));
|
||||
assertThat(thirdIdentifier.getSystem(), is(equalTo(myMdmSettings.getMdmRules().getEnterpriseEIDSystemForResourceType("Patient"))));
|
||||
assertThat(thirdIdentifier.getValue(), is(equalTo("67890")));
|
||||
}
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
Patient patient = getTargetResourceFromMdmLink(mdmLink.get(), "Patient");
|
||||
List<CanonicalEID> externalEid = myEidHelper.getExternalEid(patient);
|
||||
|
||||
assertThat(externalEid.get(0).getSystem(), is(equalTo(myMdmSettings.getMdmRules().getEnterpriseEIDSystem())));
|
||||
assertThat(externalEid.get(0).getSystem(), is(equalTo(myMdmSettings.getMdmRules().getEnterpriseEIDSystemForResourceType("Patient"))));
|
||||
assertThat(externalEid.get(0).getValue(), is(equalTo(sampleEID)));
|
||||
}
|
||||
|
||||
|
@ -222,7 +222,7 @@ public class MdmMatchLinkSvcTest extends BaseMdmR4Test {
|
|||
|
||||
//The collision should have added a new identifier with the external system.
|
||||
Identifier secondIdentifier = identifier.get(1);
|
||||
assertThat(secondIdentifier.getSystem(), is(equalTo(myMdmSettings.getMdmRules().getEnterpriseEIDSystem())));
|
||||
assertThat(secondIdentifier.getSystem(), is(equalTo(myMdmSettings.getMdmRules().getEnterpriseEIDSystemForResourceType("Patient"))));
|
||||
assertThat(secondIdentifier.getValue(), is(equalTo("12345")));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package ca.uhn.fhir.jpa.subscription.api;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This is used by "message" type subscriptions to provide a key to the message wrapper before submitting it to the channel
|
||||
*/
|
||||
public interface ISubscriptionMessageKeySvc {
|
||||
/**
|
||||
* Given an {@link IBaseResource}, return a key that can be used to identify the message. This key will be used to
|
||||
* partition the message into a queue.
|
||||
*
|
||||
* @param thePayloadResource the payload resource.
|
||||
* @return the key or null.
|
||||
*/
|
||||
@Nullable
|
||||
String getMessageKeyOrNull(IBaseResource thePayloadResource);
|
||||
}
|
|
@ -64,6 +64,7 @@ public class SubscriptionDeliveringMessageSubscriber extends BaseSubscriptionDel
|
|||
|
||||
protected void doDelivery(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, IChannelProducer theChannelProducer, IBaseResource thePayloadResource) {
|
||||
ResourceModifiedMessage payload = new ResourceModifiedMessage(myFhirContext, thePayloadResource, theMsg.getOperationType());
|
||||
payload.setMessageKey(theMsg.getMessageKeyOrNull());
|
||||
payload.setTransactionId(theMsg.getTransactionId());
|
||||
ResourceModifiedJsonMessage message = new ResourceModifiedJsonMessage(payload);
|
||||
theChannelProducer.send(message);
|
||||
|
|
|
@ -16,10 +16,13 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class CanonicalSubscriptionTest {
|
||||
|
||||
private static final String TAG_SYSTEM = "https://hapifhir.org/NamingSystem/managing-mdm-system";
|
||||
private static final String TAG_VALUE = "HAPI-MDM";
|
||||
@Test
|
||||
public void testGetChannelExtension() throws IOException {
|
||||
|
||||
|
@ -54,6 +57,14 @@ public class CanonicalSubscriptionTest {
|
|||
assertThat(s.getChannelExtensions("key3"), Matchers.empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanonicalSubscriptionRetainsMetaTags() throws IOException {
|
||||
SubscriptionCanonicalizer canonicalizer = new SubscriptionCanonicalizer(FhirContext.forR4());
|
||||
CanonicalSubscription sub1 = canonicalizer.canonicalize(makeMdmSubscription());
|
||||
assertTrue(sub1.getTags().keySet().contains(TAG_SYSTEM));
|
||||
assertEquals(sub1.getTags().get(TAG_SYSTEM), TAG_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emailDetailsEquals() {
|
||||
SubscriptionCanonicalizer canonicalizer = new SubscriptionCanonicalizer(FhirContext.forR4());
|
||||
|
@ -69,6 +80,14 @@ public class CanonicalSubscriptionTest {
|
|||
retVal.setChannel(channel);
|
||||
return retVal;
|
||||
}
|
||||
private Subscription makeMdmSubscription() {
|
||||
Subscription retVal = new Subscription();
|
||||
Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent();
|
||||
channel.setType(Subscription.SubscriptionChannelType.MESSAGE);
|
||||
retVal.setChannel(channel);
|
||||
retVal.getMeta().addTag("https://hapifhir.org/NamingSystem/managing-mdm-system", "HAPI-MDM", "managed by hapi mdm");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private CanonicalSubscription serializeAndDeserialize(CanonicalSubscription theSubscription) throws IOException {
|
||||
|
||||
|
|
|
@ -35,17 +35,21 @@ import ca.uhn.fhir.mdm.rules.json.MdmSimilarityJson;
|
|||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.xml.crypto.Data;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class MdmRuleValidator implements IMdmRuleValidator {
|
||||
|
@ -73,7 +77,40 @@ public class MdmRuleValidator implements IMdmRuleValidator {
|
|||
validateMdmTypes(theMdmRules);
|
||||
validateSearchParams(theMdmRules);
|
||||
validateMatchFields(theMdmRules);
|
||||
validateSystemIsUri(theMdmRules);
|
||||
validateSystemsAreUris(theMdmRules);
|
||||
validateEidSystemsMatchMdmTypes(theMdmRules);
|
||||
}
|
||||
|
||||
private void validateEidSystemsMatchMdmTypes(MdmRulesJson theMdmRules) {
|
||||
theMdmRules.getEnterpriseEIDSystems().keySet()
|
||||
.forEach(key -> {
|
||||
//Ensure each key is either * or a valid resource type.
|
||||
if (!key.equalsIgnoreCase("*") && !theMdmRules.getMdmTypes().contains(key)) {
|
||||
throw new ConfigurationException(String.format("There is an eidSystem set for [%s] but that is not one of the mdmTypes. Valid options are [%s].", key, buildValidEidKeysMessage(theMdmRules)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String buildValidEidKeysMessage(MdmRulesJson theMdmRulesJson) {
|
||||
List<String> validTypes = new ArrayList<>(theMdmRulesJson.getMdmTypes());
|
||||
validTypes.add("*");
|
||||
return String.join(", ", validTypes);
|
||||
}
|
||||
|
||||
private void validateSystemsAreUris(MdmRulesJson theMdmRules) {
|
||||
theMdmRules.getEnterpriseEIDSystems().entrySet()
|
||||
.forEach(entry -> {
|
||||
String resourceType = entry.getKey();
|
||||
String uri = entry.getValue();
|
||||
if (!resourceType.equals("*")) {
|
||||
try {
|
||||
myFhirContext.getResourceType(resourceType);
|
||||
}catch (DataFormatException e) {
|
||||
throw new ConfigurationException(String.format("%s is not a valid resource type, but is set in the eidSystems field.", resourceType));
|
||||
}
|
||||
}
|
||||
validateIsUri(uri);
|
||||
});
|
||||
}
|
||||
|
||||
public void validateMdmTypes(MdmRulesJson theMdmRulesJson) {
|
||||
|
@ -212,15 +249,10 @@ public class MdmRuleValidator implements IMdmRuleValidator {
|
|||
validateFieldPathForType(theFieldMatch.getResourceType(), theFieldMatch);
|
||||
}
|
||||
|
||||
private void validateSystemIsUri(MdmRulesJson theMdmRulesJson) {
|
||||
if (theMdmRulesJson.getEnterpriseEIDSystem() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ourLog.info("Validating system URI {}", theMdmRulesJson.getEnterpriseEIDSystem());
|
||||
|
||||
private void validateIsUri(String theUri) {
|
||||
ourLog.info("Validating system URI {}", theUri);
|
||||
try {
|
||||
new URI(theMdmRulesJson.getEnterpriseEIDSystem());
|
||||
new URI(theUri);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new ConfigurationException("Enterprise Identifier System (eidSystem) must be a valid URI");
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.util.StdConverter;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -34,6 +35,8 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static ca.uhn.fhir.mdm.api.MdmConstants.ALL_RESOURCE_SEARCH_PARAM_TYPE;
|
||||
|
||||
@JsonDeserialize(converter = MdmRulesJson.MdmRulesJsonConverter.class)
|
||||
public class MdmRulesJson implements IModelJson {
|
||||
|
||||
|
@ -47,10 +50,16 @@ public class MdmRulesJson implements IModelJson {
|
|||
List<MdmFieldMatchJson> myMatchFieldJsonList = new ArrayList<>();
|
||||
@JsonProperty(value = "matchResultMap", required = true)
|
||||
Map<String, MdmMatchResultEnum> myMatchResultMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* This field is deprecated, use eidSystems instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@JsonProperty(value = "eidSystem")
|
||||
String myEnterpriseEIDSystem;
|
||||
|
||||
|
||||
@JsonProperty(value = "eidSystems")
|
||||
Map<String, String> myEnterpriseEidSystems = new HashMap<>();
|
||||
@JsonProperty(value = "mdmTypes")
|
||||
List<String> myMdmTypes;
|
||||
|
||||
|
@ -112,14 +121,57 @@ public class MdmRulesJson implements IModelJson {
|
|||
return Collections.unmodifiableList(myCandidateFilterSearchParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link this#getEnterpriseEIDSystemForResourceType(String)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public String getEnterpriseEIDSystem() {
|
||||
return myEnterpriseEIDSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link this#setEnterpriseEIDSystems(Map)} (String)} or {@link this#addEnterpriseEIDSystem(String, String)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setEnterpriseEIDSystem(String theEnterpriseEIDSystem) {
|
||||
myEnterpriseEIDSystem = theEnterpriseEIDSystem;
|
||||
}
|
||||
|
||||
public void setEnterpriseEIDSystems(Map<String, String> theEnterpriseEIDSystems) {
|
||||
myEnterpriseEidSystems = theEnterpriseEIDSystems;
|
||||
}
|
||||
|
||||
public void addEnterpriseEIDSystem(String theResourceType, String theEidSystem) {
|
||||
if (myEnterpriseEidSystems == null) {
|
||||
myEnterpriseEidSystems = new HashMap<>();
|
||||
}
|
||||
myEnterpriseEidSystems.put(theResourceType, theEidSystem);
|
||||
}
|
||||
|
||||
public Map<String, String> getEnterpriseEIDSystems() {
|
||||
//First try the new property.
|
||||
if (myEnterpriseEidSystems != null && !myEnterpriseEidSystems.isEmpty()) {
|
||||
return myEnterpriseEidSystems;
|
||||
//If that fails, fall back to our deprecated property.
|
||||
} else if (!StringUtils.isBlank(myEnterpriseEIDSystem)) {
|
||||
HashMap<String , String> retVal = new HashMap<>();
|
||||
retVal.put(ALL_RESOURCE_SEARCH_PARAM_TYPE, myEnterpriseEIDSystem);
|
||||
return retVal;
|
||||
//Otherwise, return an empty map.
|
||||
} else {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
public String getEnterpriseEIDSystemForResourceType(String theResourceType) {
|
||||
Map<String, String> enterpriseEIDSystems = getEnterpriseEIDSystems();
|
||||
if (enterpriseEIDSystems.containsKey(ALL_RESOURCE_SEARCH_PARAM_TYPE)) {
|
||||
return enterpriseEIDSystems.get(ALL_RESOURCE_SEARCH_PARAM_TYPE);
|
||||
} else {
|
||||
return enterpriseEIDSystems.get(theResourceType);
|
||||
}
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return myVersion;
|
||||
}
|
||||
|
@ -131,6 +183,14 @@ public class MdmRulesJson implements IModelJson {
|
|||
|
||||
private void validate() {
|
||||
Validate.notBlank(myVersion, "version may not be blank");
|
||||
|
||||
Map<String, String> enterpriseEIDSystems = getEnterpriseEIDSystems();
|
||||
|
||||
//If we have a * eid system, there should only be one.
|
||||
if (enterpriseEIDSystems.containsKey(ALL_RESOURCE_SEARCH_PARAM_TYPE)) {
|
||||
Validate.isTrue(enterpriseEIDSystems.size() == 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String getSummary() {
|
||||
|
|
|
@ -63,7 +63,8 @@ public final class EIDHelper {
|
|||
* @return An optional {@link CanonicalEID} representing the external EID. Absent if the EID is not present.
|
||||
*/
|
||||
public List<CanonicalEID> getExternalEid(IBaseResource theResource) {
|
||||
return CanonicalEID.extractFromResource(myFhirContext, myMdmSettings.getMdmRules().getEnterpriseEIDSystem(), theResource);
|
||||
String resourceType = myFhirContext.getResourceType(theResource);
|
||||
return CanonicalEID.extractFromResource(myFhirContext, myMdmSettings.getMdmRules().getEnterpriseEIDSystemForResourceType(resourceType), theResource);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -120,7 +120,7 @@ public class GoldenResourceHelper {
|
|||
}
|
||||
|
||||
private void cloneAllExternalEidsIntoNewGoldenResource(BaseRuntimeChildDefinition theGoldenResourceIdentifier,
|
||||
IBase theGoldenResource, IBase theNewGoldenResource) {
|
||||
IAnyResource theGoldenResource, IBase theNewGoldenResource) {
|
||||
// FHIR choice types - fields within fhir where we have a choice of ids
|
||||
IFhirPath fhirPath = myFhirContext.newFhirPath();
|
||||
List<IBase> goldenResourceIdentifiers = theGoldenResourceIdentifier.getAccessor().getValues(theGoldenResource);
|
||||
|
@ -128,7 +128,8 @@ public class GoldenResourceHelper {
|
|||
for (IBase base : goldenResourceIdentifiers) {
|
||||
Optional<IPrimitiveType> system = fhirPath.evaluateFirst(base, "system", IPrimitiveType.class);
|
||||
if (system.isPresent()) {
|
||||
String mdmSystem = myMdmSettings.getMdmRules().getEnterpriseEIDSystem();
|
||||
String resourceType = myFhirContext.getResourceType(theGoldenResource);
|
||||
String mdmSystem = myMdmSettings.getMdmRules().getEnterpriseEIDSystemForResourceType(resourceType);
|
||||
String baseSystem = system.get().getValueAsString();
|
||||
if (Objects.equals(baseSystem, mdmSystem)) {
|
||||
ca.uhn.fhir.util.TerserUtil.cloneEidIntoResource(myFhirContext, theGoldenResourceIdentifier, base, theNewGoldenResource);
|
||||
|
@ -188,7 +189,7 @@ public class GoldenResourceHelper {
|
|||
return theGoldenResource;
|
||||
}
|
||||
|
||||
private void clearExternalEidsFromTheGoldenResource(BaseRuntimeChildDefinition theGoldenResourceIdentifier, IBase theGoldenResource) {
|
||||
private void clearExternalEidsFromTheGoldenResource(BaseRuntimeChildDefinition theGoldenResourceIdentifier, IBaseResource theGoldenResource) {
|
||||
IFhirPath fhirPath = myFhirContext.newFhirPath();
|
||||
List<IBase> goldenResourceIdentifiers = theGoldenResourceIdentifier.getAccessor().getValues(theGoldenResource);
|
||||
List<IBase> clonedIdentifiers = new ArrayList<>();
|
||||
|
@ -197,7 +198,8 @@ public class GoldenResourceHelper {
|
|||
for (IBase base : goldenResourceIdentifiers) {
|
||||
Optional<IPrimitiveType> system = fhirPath.evaluateFirst(base, "system", IPrimitiveType.class);
|
||||
if (system.isPresent()) {
|
||||
String mdmSystem = myMdmSettings.getMdmRules().getEnterpriseEIDSystem();
|
||||
String resourceType = myFhirContext.getResourceType(theGoldenResource);
|
||||
String mdmSystem = myMdmSettings.getMdmRules().getEnterpriseEIDSystemForResourceType(resourceType);
|
||||
String baseSystem = system.get().getValueAsString();
|
||||
if (Objects.equals(baseSystem, mdmSystem)) {
|
||||
ourLog.debug("Found EID confirming to MDM rules {}. It should not be copied, skipping", baseSystem);
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.springframework.core.io.Resource;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
@ -161,6 +162,25 @@ public class MdmRuleValidatorTest extends BaseR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadEidResourceType() throws IOException {
|
||||
try {
|
||||
setMdmRuleJson("bad-rules-illegal-resource-type-eid.json");
|
||||
}
|
||||
catch (ConfigurationException e){
|
||||
assertThat(e.getMessage(), is(equalTo("not-a-resource is not a valid resource type, but is set in the eidSystems field.")));
|
||||
}
|
||||
}
|
||||
@Test
|
||||
public void testBadEidDoesntMatchKnownMdmTypes() throws IOException {
|
||||
try {
|
||||
setMdmRuleJson("bad-rules-illegal-missing-resource-type.json");
|
||||
}
|
||||
catch (ConfigurationException e){
|
||||
assertThat(e.getMessage(), is(equalTo("There is an eidSystem set for [Patient] but that is not one of the mdmTypes. Valid options are [Organization, *].")));
|
||||
}
|
||||
}
|
||||
|
||||
private void setMdmRuleJson(String theTheS) throws IOException {
|
||||
MdmRuleValidator mdmRuleValidator = new MdmRuleValidator(ourFhirContext, mySearchParamRetriever);
|
||||
MdmSettings mdmSettings = new MdmSettings(mdmRuleValidator);
|
||||
|
|
|
@ -11,9 +11,13 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
@ -72,4 +76,32 @@ public class MdmRulesJsonR4Test extends BaseMdmRulesR4Test {
|
|||
assertEquals("There is no matchField with name bad", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidResourceTypeDoesntDeserialize() throws IOException {
|
||||
myRules = buildOldStyleEidRules();
|
||||
|
||||
String eidSystem = myRules.getEnterpriseEIDSystemForResourceType("Patient");
|
||||
assertThat(eidSystem, is(equalTo(PATIENT_EID_FOR_TEST)));
|
||||
|
||||
eidSystem = myRules.getEnterpriseEIDSystemForResourceType("Practitioner");
|
||||
assertThat(eidSystem, is(equalTo(PATIENT_EID_FOR_TEST)));
|
||||
|
||||
eidSystem = myRules.getEnterpriseEIDSystemForResourceType("Medication");
|
||||
assertThat(eidSystem, is(equalTo(PATIENT_EID_FOR_TEST)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MdmRulesJson buildActiveBirthdateIdRules() {
|
||||
return super.buildActiveBirthdateIdRules();
|
||||
}
|
||||
|
||||
private MdmRulesJson buildOldStyleEidRules() {
|
||||
MdmRulesJson mdmRulesJson = super.buildActiveBirthdateIdRules();
|
||||
mdmRulesJson.setEnterpriseEIDSystems(Collections.emptyMap());
|
||||
//This sets the new-style eid resource type to `*`
|
||||
mdmRulesJson.setEnterpriseEIDSystem(PATIENT_EID_FOR_TEST);
|
||||
return mdmRulesJson;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@ public abstract class BaseMdmRulesR4Test extends BaseR4Test {
|
|||
public static final String PATIENT_GIVEN = "patient-given";
|
||||
public static final String PATIENT_GIVEN_FIRST = "patient-given-first";
|
||||
public static final String PATIENT_FAMILY = "patient-last";
|
||||
public static final String PATIENT_EID_FOR_TEST = "http://hello.com/naming/patient-eid";
|
||||
public static final String MEDICATION_EID_FOR_TEST= "http://hello.com/naming/medication-eid";
|
||||
public static final String PRACTITIONER_EID_FOR_TEST = "http://hello.com/naming/practitioner-eid";
|
||||
|
||||
public static final double NAME_THRESHOLD = 0.8;
|
||||
protected MdmFieldMatchJson myGivenNameMatchField;
|
||||
|
@ -71,6 +74,10 @@ public abstract class BaseMdmRulesR4Test extends BaseR4Test {
|
|||
retval.setMdmTypes(Arrays.asList("Patient", "Practitioner", "Medication"));
|
||||
retval.putMatchResult(myBothNameFields, MdmMatchResultEnum.MATCH);
|
||||
retval.putMatchResult(PATIENT_GIVEN, MdmMatchResultEnum.POSSIBLE_MATCH);
|
||||
|
||||
retval.addEnterpriseEIDSystem("Patient", PATIENT_EID_FOR_TEST);
|
||||
retval.addEnterpriseEIDSystem("Medication", MEDICATION_EID_FOR_TEST);
|
||||
retval.addEnterpriseEIDSystem("Practitioner", PRACTITIONER_EID_FOR_TEST);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ public class EIDHelperR4Test extends BaseR4Test {
|
|||
|
||||
private static final MdmRulesJson ourRules = new MdmRulesJson() {
|
||||
{
|
||||
setEnterpriseEIDSystem(EXTERNAL_ID_SYSTEM_FOR_TEST);
|
||||
addEnterpriseEIDSystem("Patient", EXTERNAL_ID_SYSTEM_FOR_TEST);
|
||||
setMdmTypes(Arrays.asList(new String[] {"Patient"}));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"version": "1",
|
||||
"mdmTypes": ["Organization"],
|
||||
"candidateSearchParams": [],
|
||||
"candidateFilterSearchParams": [],
|
||||
"matchFields": [
|
||||
{
|
||||
"name": "name-prefix",
|
||||
"resourceType": "Organization",
|
||||
"resourcePath": "name",
|
||||
"matcher": {
|
||||
"algorithm": "STRING"
|
||||
}
|
||||
}
|
||||
],
|
||||
"matchResultMap": {},
|
||||
"eidSystems": {
|
||||
"Patient": "http://hello.com/uri"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"version": "1",
|
||||
"mdmTypes": ["Organization"],
|
||||
"candidateSearchParams": [],
|
||||
"candidateFilterSearchParams": [],
|
||||
"matchFields": [
|
||||
{
|
||||
"name": "name-prefix",
|
||||
"resourceType": "Organization",
|
||||
"resourcePath": "name",
|
||||
"matcher": {
|
||||
"algorithm": "STRING"
|
||||
}
|
||||
}
|
||||
],
|
||||
"matchResultMap": {},
|
||||
"eidSystems": {
|
||||
"not-a-resource": "http://hello.com/uri"
|
||||
}
|
||||
}
|
|
@ -46,6 +46,12 @@ public abstract class BaseResourceMessage implements IResourceMessage, IModelJso
|
|||
@JsonProperty("mediaType")
|
||||
private String myMediaType;
|
||||
|
||||
/**
|
||||
* This is used by any message going to kafka for topic partition selection purposes.
|
||||
*/
|
||||
@JsonProperty("messageKey")
|
||||
private String myMessageKey;
|
||||
|
||||
/**
|
||||
* Returns an attribute stored in this message.
|
||||
* <p>
|
||||
|
@ -156,6 +162,15 @@ public abstract class BaseResourceMessage implements IResourceMessage, IModelJso
|
|||
myMediaType = theMediaType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMessageKeyOrNull() {
|
||||
return myMessageKey;
|
||||
}
|
||||
|
||||
public void setMessageKey(String theMessageKey) {
|
||||
myMessageKey = theMessageKey;
|
||||
}
|
||||
|
||||
public enum OperationTypeEnum {
|
||||
CREATE,
|
||||
UPDATE,
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||
|
||||
public class ResourceOperationMessage extends BaseResourceModifiedMessage {
|
||||
|
||||
|
||||
public ResourceOperationMessage() {
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public abstract class BaseJsonMessage<T> implements Message<T>, IModelJson {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
@ -58,4 +60,9 @@ public abstract class BaseJsonMessage<T> implements Message<T>, IModelJson {
|
|||
public void setHeaders(HapiMessageHeaders theHeaders) {
|
||||
myHeaders = theHeaders;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMessageKeyOrNull() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!-- FIXME KHS remove jpa stuff from here -->
|
||||
<!-- TODO KHS remove jpa stuff from here -->
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-jpaserver-model</artifactId>
|
||||
|
|
|
@ -25,11 +25,14 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy;
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
|
||||
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.util.HapiExtensions;
|
||||
import org.hl7.fhir.dstu3.model.Coding;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
||||
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
|
||||
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
|
@ -79,7 +82,6 @@ public class SubscriptionCanonicalizer {
|
|||
|
||||
private CanonicalSubscription canonicalizeDstu2(IBaseResource theSubscription) {
|
||||
ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription;
|
||||
|
||||
CanonicalSubscription retVal = new CanonicalSubscription();
|
||||
try {
|
||||
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus()));
|
||||
|
@ -90,12 +92,27 @@ public class SubscriptionCanonicalizer {
|
|||
retVal.setChannelExtensions(extractExtension(subscription));
|
||||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getChannel().getPayload());
|
||||
retVal.setTags(extractTags(subscription));
|
||||
} catch (FHIRException theE) {
|
||||
throw new InternalErrorException(theE);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the meta tags from the subscription and convert them to a simple string map.
|
||||
* @param theSubscription The subscription to extract the tags from
|
||||
* @return A map of tags System:Code
|
||||
*/
|
||||
private Map<String, String> extractTags(IBaseResource theSubscription) {
|
||||
return theSubscription.getMeta().getTag()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(
|
||||
IBaseCoding::getSystem,
|
||||
IBaseCoding::getCode
|
||||
));
|
||||
}
|
||||
|
||||
private CanonicalSubscription canonicalizeDstu3(IBaseResource theSubscription) {
|
||||
org.hl7.fhir.dstu3.model.Subscription subscription = (org.hl7.fhir.dstu3.model.Subscription) theSubscription;
|
||||
|
||||
|
@ -113,6 +130,7 @@ public class SubscriptionCanonicalizer {
|
|||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getChannel().getPayload());
|
||||
retVal.setPayloadSearchCriteria(getExtensionString(subscription, HapiExtensions.EXT_SUBSCRIPTION_PAYLOAD_SEARCH_CRITERIA));
|
||||
retVal.setTags(extractTags(subscription));
|
||||
|
||||
if (retVal.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) {
|
||||
String from;
|
||||
|
@ -201,7 +219,6 @@ public class SubscriptionCanonicalizer {
|
|||
|
||||
private CanonicalSubscription canonicalizeR4(IBaseResource theSubscription) {
|
||||
org.hl7.fhir.r4.model.Subscription subscription = (org.hl7.fhir.r4.model.Subscription) theSubscription;
|
||||
|
||||
CanonicalSubscription retVal = new CanonicalSubscription();
|
||||
retVal.setStatus(subscription.getStatus());
|
||||
retVal.setChannelType(getChannelType(theSubscription));
|
||||
|
@ -212,6 +229,7 @@ public class SubscriptionCanonicalizer {
|
|||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getChannel().getPayload());
|
||||
retVal.setPayloadSearchCriteria(getExtensionString(subscription, HapiExtensions.EXT_SUBSCRIPTION_PAYLOAD_SEARCH_CRITERIA));
|
||||
retVal.setTags(extractTags(subscription));
|
||||
|
||||
if (retVal.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) {
|
||||
String from;
|
||||
|
@ -266,6 +284,7 @@ public class SubscriptionCanonicalizer {
|
|||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getContentType());
|
||||
retVal.setPayloadSearchCriteria(getExtensionString(subscription, HapiExtensions.EXT_SUBSCRIPTION_PAYLOAD_SEARCH_CRITERIA));
|
||||
retVal.setTags(extractTags(subscription));
|
||||
|
||||
if (retVal.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) {
|
||||
String from;
|
||||
|
|
|
@ -68,6 +68,8 @@ public class CanonicalSubscription implements Serializable, Cloneable, IModelJso
|
|||
private RestHookDetails myRestHookDetails;
|
||||
@JsonProperty("extensions")
|
||||
private Map<String, List<String>> myChannelExtensions;
|
||||
@JsonProperty("tags")
|
||||
private Map<String, String> myTags;
|
||||
@JsonProperty("payloadSearchCriteria")
|
||||
private String myPayloadSearchCriteria;
|
||||
|
||||
|
@ -139,6 +141,15 @@ public class CanonicalSubscription implements Serializable, Cloneable, IModelJso
|
|||
}
|
||||
}
|
||||
|
||||
public Map<String, String> getTags() {
|
||||
return myTags;
|
||||
}
|
||||
|
||||
public void setTags(Map<String, String> theTags) {
|
||||
this.myTags = theTags;
|
||||
}
|
||||
|
||||
|
||||
public void setHeaders(String theHeaders) {
|
||||
myHeaders = new ArrayList<>();
|
||||
if (isNotBlank(theHeaders)) {
|
||||
|
|
|
@ -24,6 +24,8 @@ import ca.uhn.fhir.rest.server.messaging.json.BaseJsonMessage;
|
|||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ResourceModifiedJsonMessage extends BaseJsonMessage<ResourceModifiedMessage> {
|
||||
|
||||
@JsonProperty("payload")
|
||||
|
@ -52,6 +54,15 @@ public class ResourceModifiedJsonMessage extends BaseJsonMessage<ResourceModifie
|
|||
myPayload = thePayload;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getMessageKeyOrNull() {
|
||||
if (myPayload == null) {
|
||||
return null;
|
||||
}
|
||||
return myPayload.getMessageKeyOrNull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
|
|
|
@ -27,6 +27,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Most of this class has been moved to ResourceModifiedMessage in the hapi-fhir-server project, for a reusable channel ResourceModifiedMessage
|
||||
* that doesn't require knowledge of subscriptions.
|
||||
|
@ -40,6 +42,8 @@ public class ResourceModifiedMessage extends BaseResourceModifiedMessage {
|
|||
@JsonProperty(value = "subscriptionId", required = false)
|
||||
private String mySubscriptionId;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -64,6 +68,7 @@ public class ResourceModifiedMessage extends BaseResourceModifiedMessage {
|
|||
mySubscriptionId = theSubscriptionId;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
|
|
Loading…
Reference in New Issue