From 300cbcb1b22bbed36e33b6c6fbcb890528520bc0 Mon Sep 17 00:00:00 2001 From: Pawel Mankowski Date: Mon, 3 Oct 2022 18:06:25 -0400 Subject: [PATCH 1/6] [HIE-2] client registry Patient search flow implementation [HIE-2] define ihe-pix operation and start implementation of getCRPatient [HIE-2] adding config options for default system and get patient endpoint [HIE-2] drop format param and change targetSystems to OR [HIE-2] move targetSystem param logic to controller [HIE-2] change to OperationParam in getCRPatient provider method cleanup, adding comments, refactoring Patient parsing --- .../clientregistry/ClientRegistryConfig.java | 21 +++- .../ClientRegistryConstants.java | 8 +- .../ClientRegistryTransactionType.java | 6 ++ .../clientregistry/api/CRPatientService.java | 14 +++ .../api/ClientRegistryManager.java | 35 ++++++ .../api/impl/FhirCRPatientServiceImpl.java | 67 ++++++++++++ .../providers/FhirCRConstants.java | 26 +++++ .../r4/FhirCRPatientResourceProvider.java | 102 ++++++++++++++++++ omod/src/main/resources/config.xml | 28 ++++- pom.xml | 5 +- 10 files changed, 306 insertions(+), 6 deletions(-) create mode 100644 api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryTransactionType.java create mode 100644 api/src/main/java/org/openmrs/module/clientregistry/api/CRPatientService.java create mode 100644 api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java create mode 100644 api/src/main/java/org/openmrs/module/clientregistry/providers/FhirCRConstants.java create mode 100644 api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java diff --git a/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConfig.java b/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConfig.java index b7bb866..574e7f7 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConfig.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConfig.java @@ -10,6 +10,7 @@ import org.apache.commons.lang.StringUtils; import org.openmrs.api.AdministrationService; +import org.openmrs.module.clientregistry.providers.FhirCRConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -46,7 +47,21 @@ public boolean clientRegistryConnectionEnabled() { public String getClientRegistryServerUrl() { return serverUrl; } - + + public String getClientRegistryGetPatientEndpoint() { + String globalPropPatientEndpoint = administrationService + .getGlobalProperty(ClientRegistryConstants.GP_FHIR_CLIENT_REGISTRY_GET_PATIENT_ENDPOINT); + + // default to Patient/$ihe-pix if patient endpoint is not defined in config + return (globalPropPatientEndpoint == null || globalPropPatientEndpoint.isEmpty()) ? String.format("Patient/%s", + FhirCRConstants.IHE_PIX_OPERATION) : globalPropPatientEndpoint; + } + + public String getClientRegistryDefaultPatientIdentifierSystem() { + return administrationService + .getGlobalProperty(ClientRegistryConstants.GP_CLIENT_REGISTRY_DEFAULT_PATIENT_IDENTIFIER_SYSTEM); + } + public String getClientRegistryUserName() { return username; } @@ -58,4 +73,8 @@ public String getClientRegistryPassword() { public String getClientRegistryIdentifierRoot() { return identifierRoot; } + + public String getClientRegistryTransactionMethod() { + return administrationService.getGlobalProperty(ClientRegistryConstants.GP_CLIENT_REGISTRY_TRANSACTION_METHOD); + } } diff --git a/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConstants.java b/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConstants.java index f10b6b0..bde5225 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConstants.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryConstants.java @@ -2,7 +2,11 @@ public class ClientRegistryConstants { - public static final String GP_CLIENT_REGISTRY_SERVER_URL = "clientregistry.serverUrl"; + public static final String GP_CLIENT_REGISTRY_SERVER_URL = "clientregistry.clientRegistryServerUrl"; + + public static final String GP_FHIR_CLIENT_REGISTRY_GET_PATIENT_ENDPOINT = "clientregistry.fhirGetPatientEndpoint"; + + public static final String GP_CLIENT_REGISTRY_DEFAULT_PATIENT_IDENTIFIER_SYSTEM = "clientregistry.defaultPatientIdentifierSystem"; public static final String GP_CLIENT_REGISTRY_USER_NAME = "clientregistry.username"; @@ -10,6 +14,8 @@ public class ClientRegistryConstants { public static final String GP_CLIENT_REGISTRY_IDENTIFIER_ROOT = "clientregistry.identifierRoot"; + public static final String GP_CLIENT_REGISTRY_TRANSACTION_METHOD = "clientregistry.transactionMethod"; + public static final String UPDATE_MESSAGE_DESTINATION = "topic://UPDATED:org.openmrs.Patient"; public static final String CLIENT_REGISTRY_INTERNAL_ID_SYSTEM = "http://clientregistry.org/openmrs"; diff --git a/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryTransactionType.java b/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryTransactionType.java new file mode 100644 index 0000000..57d83f1 --- /dev/null +++ b/api/src/main/java/org/openmrs/module/clientregistry/ClientRegistryTransactionType.java @@ -0,0 +1,6 @@ +package org.openmrs.module.clientregistry; + +public enum ClientRegistryTransactionType { + FHIR, + HL7 +} diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/CRPatientService.java b/api/src/main/java/org/openmrs/module/clientregistry/api/CRPatientService.java new file mode 100644 index 0000000..93a2369 --- /dev/null +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/CRPatientService.java @@ -0,0 +1,14 @@ +package org.openmrs.module.clientregistry.api; + +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.hl7.fhir.r4.model.Patient; +import org.openmrs.module.fhir2.api.search.param.PatientSearchParams; + +import java.util.List; + +public interface CRPatientService { + + List getCRPatient(String sourceIdentifier, String sourceIdentifierSystem, List extraTargetSystems); + + List searchCRForPatients(PatientSearchParams patientSearchParams); +} diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/ClientRegistryManager.java b/api/src/main/java/org/openmrs/module/clientregistry/api/ClientRegistryManager.java index 3b82308..512921f 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/api/ClientRegistryManager.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/ClientRegistryManager.java @@ -10,8 +10,11 @@ import org.openmrs.api.GlobalPropertyListener; import org.openmrs.event.Event; import org.openmrs.module.DaemonToken; +import org.openmrs.module.clientregistry.ClientRegistryConfig; import org.openmrs.module.clientregistry.ClientRegistryConstants; +import org.openmrs.module.clientregistry.ClientRegistryTransactionType; import org.openmrs.module.clientregistry.api.event.PatientCreateUpdateListener; +import org.openmrs.module.clientregistry.api.impl.FhirCRPatientServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -27,6 +30,12 @@ public class ClientRegistryManager implements GlobalPropertyListener { @Autowired private PatientCreateUpdateListener patientListener; + @Autowired + private FhirCRPatientServiceImpl fhirPatientService; + + @Autowired + private ClientRegistryConfig clientRegistryConfig; + public void setDaemonToken(DaemonToken daemonToken) { this.daemonToken = daemonToken; } @@ -82,4 +91,30 @@ public void disableClientRegistry() { isRunning.set(false); } + + /** + * Determine the appropriate PatientService class based off of the client registry transaction + * type configuration + * + * @return PatientService class corresponding to the appropriate transaction type supported by + * the client registry + * @throws IllegalArgumentException if defined transaction type is unsupported + */ + public CRPatientService getPatientService() throws IllegalArgumentException { + try { + String transactionMethodGlobalProperty = clientRegistryConfig.getClientRegistryTransactionMethod().toUpperCase(); + + switch (ClientRegistryTransactionType.valueOf(transactionMethodGlobalProperty)) { + case FHIR: + return fhirPatientService; + case HL7: + throw new IllegalArgumentException("HL7 transaction type is currently unsupported"); + } + } + catch (Exception ignored) { + + } + + throw new IllegalArgumentException("Unsupported transaction type"); + } } diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java b/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java new file mode 100644 index 0000000..ee338c7 --- /dev/null +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java @@ -0,0 +1,67 @@ +package org.openmrs.module.clientregistry.api.impl; + +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.gclient.IQuery; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Patient; +import org.openmrs.module.clientregistry.ClientRegistryConfig; +import org.openmrs.module.clientregistry.api.CRPatientService; +import org.openmrs.module.clientregistry.providers.FhirCRConstants; +import org.openmrs.module.fhir2.FhirConstants; +import org.openmrs.module.fhir2.api.search.param.PatientSearchParams; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class FhirCRPatientServiceImpl implements CRPatientService { + + @Autowired + private IGenericClient fhirClient; + + @Autowired + private ClientRegistryConfig config; + + /** + * Constructs a $ihe-pix fhir client call to an external Client Registry returning any patients that match the given + * identifier and target systems. + */ + @Override + public List getCRPatient(String sourceIdentifier, String sourceIdentifierSystem, List extraTargetSystems) { + // construct and send request to external client registry + IQuery crRequest = fhirClient + .search() + .byUrl( + String.format("%s/%s", config.getClientRegistryServerUrl(), config.getClientRegistryGetPatientEndpoint())) + .where( + FhirCRConstants.SOURCE_IDENTIFIER_PARAM.exactly().systemAndIdentifier(sourceIdentifierSystem, + sourceIdentifier)); + + if (!extraTargetSystems.isEmpty()) { + crRequest.and(FhirCRConstants.TARGET_SYSTEM_PARAM.matches().values(extraTargetSystems)); + } + + Bundle patientBundle = crRequest.returnBundle(Bundle.class).execute(); + return parseCRPatientSearchResults(patientBundle); + } + + @Override + public List searchCRForPatients(PatientSearchParams patientSearchParams) { + return null; + } + + /** + * Filter and parse out fhir patients from Client Registry Patient Search results + */ + private List parseCRPatientSearchResults(Bundle patientBundle) { + return patientBundle + .getEntry() + .stream() + .filter(entry -> entry.hasType(FhirConstants.PATIENT)) + .map(entry -> (Patient) entry.getResource()) + .collect(Collectors.toList()); + } +} diff --git a/api/src/main/java/org/openmrs/module/clientregistry/providers/FhirCRConstants.java b/api/src/main/java/org/openmrs/module/clientregistry/providers/FhirCRConstants.java new file mode 100644 index 0000000..3ec4eea --- /dev/null +++ b/api/src/main/java/org/openmrs/module/clientregistry/providers/FhirCRConstants.java @@ -0,0 +1,26 @@ +package org.openmrs.module.clientregistry.providers; + +import ca.uhn.fhir.model.api.annotation.SearchParamDefinition; +import ca.uhn.fhir.rest.gclient.StringClientParam; +import ca.uhn.fhir.rest.gclient.TokenClientParam; +import ca.uhn.fhir.rest.gclient.UriClientParam; + +public class FhirCRConstants { + + public static final String IHE_PIX_OPERATION = "$ihe-pix"; + + @SearchParamDefinition(name = "sourceIdentifier", path = "Patient.sourceIdentifier", description = "A patient identifier used to find cross-matching identifiers in client registry", type = "token") + public static final String SOURCE_IDENTIFIER = "sourceIdentifier"; + + public static final TokenClientParam SOURCE_IDENTIFIER_PARAM = new TokenClientParam("sourceIdentifier"); + + @SearchParamDefinition(name = "targetSystem", path = "Patient.targetSystem", description = "Assigning Authorities for the Patient Identifier Domains from which the returned identifiers shall be selected", type = "token") + public static final String TARGET_SYSTEM = "targetSystem"; + + public static final UriClientParam TARGET_SYSTEM_PARAM = new UriClientParam("targetSystem"); + + @SearchParamDefinition(name = "_format", path = "Patient.targetSystem", description = "Assigning Authorities for the Patient Identifier Domains from which the returned identifiers shall be selected", type = "token") + public static final String _FORMAT = "_FORMAT"; + + public static final StringClientParam _FORMAT_PARAM = new StringClientParam("_format"); +} diff --git a/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java b/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java new file mode 100644 index 0000000..83638cd --- /dev/null +++ b/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java @@ -0,0 +1,102 @@ +package org.openmrs.module.clientregistry.providers.r4; + +import ca.uhn.fhir.model.valueset.BundleTypeEnum; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.StringOrListParam; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import lombok.Setter; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Patient; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import org.openmrs.module.clientregistry.ClientRegistryConfig; +import org.openmrs.module.clientregistry.api.ClientRegistryManager; +import org.openmrs.module.clientregistry.providers.FhirCRConstants; +import org.openmrs.module.fhir2.api.annotations.R4Provider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static lombok.AccessLevel.PACKAGE; + +@Component("crPatientFhirR4ResourceProvider") +@R4Provider +@Setter(PACKAGE) +public class FhirCRPatientResourceProvider implements IResourceProvider { + + @Autowired + private ClientRegistryManager clientRegistryManager; + + @Autowired + private ClientRegistryConfig config; + + @Override + public Class getResourceType() { + return Patient.class; + } + + /** + * FHIR endpoint to get Patient references from external client registry Example request: GET + * [fhirbase]/Patient/$ihe-pix?sourceIdentifier=1234[&targetSystem=system1,system2] + * + * @param sourceIdentifierParam patient identifier + * @param targetSystemsParam (optional) Patient assigning authorities (ie systems) from which + * the returned identifiers shall be selected. Use module defined default if not + * provided. + * @return OpenMRS Patient corresponding to identifier (TODO this might change to a list of + * identifier references returned by CR) + */ + @Operation(name = FhirCRConstants.IHE_PIX_OPERATION, idempotent=true, type = Patient.class, bundleType = BundleTypeEnum.SEARCHSET) + public List getCRPatientById( + @OperationParam(name = FhirCRConstants.SOURCE_IDENTIFIER) StringParam sourceIdentifierParam, + @OperationParam(name = FhirCRConstants.TARGET_SYSTEM) StringOrListParam targetSystemsParam + ) { + + if (sourceIdentifierParam == null || sourceIdentifierParam.getValue() == null) { + throw new InvalidRequestException("sourceIdentifier must be specified"); + } + + List targetSystems = targetSystemsParam == null + ? Collections.emptyList() + : targetSystemsParam.getValuesAsQueryTokens().stream().filter(Objects::nonNull).map(StringParam::getValue).collect(Collectors.toList()); + + // If no targetSystem provided, use config defined default. Otherwise, take first targetSystem provided and + // include in sourceIdentifier token. Remaining targetSystems included in targetSystem param passed to CR + String sourceIdentifierSystem; + if (targetSystems.isEmpty()) { + sourceIdentifierSystem = config.getClientRegistryDefaultPatientIdentifierSystem(); + } else { + sourceIdentifierSystem = targetSystems.get(0); + targetSystems.remove(0); + } + + if (sourceIdentifierSystem == null || sourceIdentifierSystem.isEmpty()) { + throw new InvalidRequestException("ClientRegistry module does not have a default target system assigned " + + "via the defaultPatientIdentifierSystem property. At least one targetSystem must be provided in " + + "the request"); + } + + List patients = clientRegistryManager.getPatientService().getCRPatient( + sourceIdentifierParam.getValue(), sourceIdentifierSystem, targetSystems + ); + + if (patients.isEmpty()) { + throw new ResourceNotFoundException("No Client Registry patients found."); + } + + return patients; + } + + @Search + public List searchClientRegistryPatients() { + throw new NotImplementedOperationException("search client registry is not yet implemented"); + } +} diff --git a/omod/src/main/resources/config.xml b/omod/src/main/resources/config.xml index 57915de..5eafd0d 100644 --- a/omod/src/main/resources/config.xml +++ b/omod/src/main/resources/config.xml @@ -32,13 +32,37 @@ - @MODULE_ID@.serverUrl - http://localhost:5001/CR/fhir/ + @MODULE_ID@.clientRegistryServerUrl + http://localhost:5001/CR/fhir Base URL for the Client Registry Server + + @MODULE_ID@.fhirGetPatientEndpoint + Patient/$ihe-pix + + Client registry endpoint implementing the Patient identifier cross-reference transaction (ITI-83) + + + + + @MODULE_ID@.defaultPatientIdentifierSystem + + + Default system from which the Patient identifiers will be returned, if no system provided in requests + + + + + @MODULE_ID@.transactionMethod + fhir + + Transaction method supported by the Client Registry. Currently supporting fhir or hl7. + + + @MODULE_ID@.username openmrs diff --git a/pom.xml b/pom.xml index eb74137..0d474cd 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ org.openmrs.tools openmrs-tools - ${openmrsPlatformVersion} + ${openmrsPlatformToolsVersion} @@ -216,7 +216,8 @@ - 1.5.1 + 1.8.0 1.11.6 + 2.4.0 From f0bf239e91067151c059980170a0b04d2dab1b22 Mon Sep 17 00:00:00 2001 From: Pawel Mankowski Date: Sun, 12 Feb 2023 15:36:12 -0500 Subject: [PATCH 2/6] [HIE-2] differentiate between source system and target systems --- .../api/impl/FhirCRPatientServiceImpl.java | 14 ++++---- .../r4/FhirCRPatientResourceProvider.java | 35 +++++++++---------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java b/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java index ee338c7..7c19f52 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java @@ -24,13 +24,13 @@ public class FhirCRPatientServiceImpl implements CRPatientService { @Autowired private ClientRegistryConfig config; - + /** - * Constructs a $ihe-pix fhir client call to an external Client Registry returning any patients that match the given - * identifier and target systems. + * Constructs a $ihe-pix fhir client call to an external Client Registry returning any patients + * that match the given source identifier/system and target systems. */ @Override - public List getCRPatient(String sourceIdentifier, String sourceIdentifierSystem, List extraTargetSystems) { + public List getCRPatient(String sourceIdentifier, String sourceIdentifierSystem, List targetSystems) { // construct and send request to external client registry IQuery crRequest = fhirClient .search() @@ -40,8 +40,8 @@ public List getCRPatient(String sourceIdentifier, String sourceIdentifi FhirCRConstants.SOURCE_IDENTIFIER_PARAM.exactly().systemAndIdentifier(sourceIdentifierSystem, sourceIdentifier)); - if (!extraTargetSystems.isEmpty()) { - crRequest.and(FhirCRConstants.TARGET_SYSTEM_PARAM.matches().values(extraTargetSystems)); + if (!targetSystems.isEmpty()) { + crRequest.and(FhirCRConstants.TARGET_SYSTEM_PARAM.matches().values(targetSystems)); } Bundle patientBundle = crRequest.returnBundle(Bundle.class).execute(); @@ -52,7 +52,7 @@ public List getCRPatient(String sourceIdentifier, String sourceIdentifi public List searchCRForPatients(PatientSearchParams patientSearchParams) { return null; } - + /** * Filter and parse out fhir patients from Client Registry Patient Search results */ diff --git a/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java b/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java index 83638cd..72feea9 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringOrListParam; +import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; @@ -45,18 +46,18 @@ public Class getResourceType() { /** * FHIR endpoint to get Patient references from external client registry Example request: GET - * [fhirbase]/Patient/$ihe-pix?sourceIdentifier=1234[&targetSystem=system1,system2] + * [fhirbase + * ]/Patient/$ihe-pix?sourceIdentifier=1234{|sourceSystem}[&targetSystem=system1,system2] * - * @param sourceIdentifierParam patient identifier + * @param sourceIdentifierParam patient identifier token. If source system is included in token, + * we will use it to override the module defined source system. * @param targetSystemsParam (optional) Patient assigning authorities (ie systems) from which - * the returned identifiers shall be selected. Use module defined default if not - * provided. - * @return OpenMRS Patient corresponding to identifier (TODO this might change to a list of - * identifier references returned by CR) + * the returned identifiers shall be selected + * @return List of matching FHIR patients returned by the client registry */ @Operation(name = FhirCRConstants.IHE_PIX_OPERATION, idempotent=true, type = Patient.class, bundleType = BundleTypeEnum.SEARCHSET) public List getCRPatientById( - @OperationParam(name = FhirCRConstants.SOURCE_IDENTIFIER) StringParam sourceIdentifierParam, + @OperationParam(name = FhirCRConstants.SOURCE_IDENTIFIER) TokenParam sourceIdentifierParam, @OperationParam(name = FhirCRConstants.TARGET_SYSTEM) StringOrListParam targetSystemsParam ) { @@ -68,20 +69,16 @@ public List getCRPatientById( ? Collections.emptyList() : targetSystemsParam.getValuesAsQueryTokens().stream().filter(Objects::nonNull).map(StringParam::getValue).collect(Collectors.toList()); - // If no targetSystem provided, use config defined default. Otherwise, take first targetSystem provided and - // include in sourceIdentifier token. Remaining targetSystems included in targetSystem param passed to CR - String sourceIdentifierSystem; - if (targetSystems.isEmpty()) { - sourceIdentifierSystem = config.getClientRegistryDefaultPatientIdentifierSystem(); - } else { - sourceIdentifierSystem = targetSystems.get(0); - targetSystems.remove(0); - } + // If no sourceSystem provided, use config defined default + boolean userDefinedSourceSystem = sourceIdentifierParam.getSystem() != null && !sourceIdentifierParam.getSystem().isEmpty(); + String sourceIdentifierSystem = userDefinedSourceSystem + ? sourceIdentifierParam.getSystem() + : config.getClientRegistryDefaultPatientIdentifierSystem(); if (sourceIdentifierSystem == null || sourceIdentifierSystem.isEmpty()) { - throw new InvalidRequestException("ClientRegistry module does not have a default target system assigned " + - "via the defaultPatientIdentifierSystem property. At least one targetSystem must be provided in " + - "the request"); + throw new InvalidRequestException("ClientRegistry module does not have a default source system assigned " + + "via the defaultPatientIdentifierSystem property. Source system must be provided as a token in " + + "the sourceIdentifier request param"); } List patients = clientRegistryManager.getPatientService().getCRPatient( From ffec8b6d11724fb00b0a6510ce9467956f320326 Mon Sep 17 00:00:00 2001 From: Pawel Mankowski Date: Mon, 13 Feb 2023 22:12:54 -0500 Subject: [PATCH 3/6] rename interface method and start on correct parsing of operation response --- .../clientregistry/api/CRPatientService.java | 3 +-- .../api/impl/FhirCRPatientServiceImpl.java | 14 ++++++++++++-- .../r4/FhirCRPatientResourceProvider.java | 4 ++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/CRPatientService.java b/api/src/main/java/org/openmrs/module/clientregistry/api/CRPatientService.java index 93a2369..f1cc9da 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/api/CRPatientService.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/CRPatientService.java @@ -1,6 +1,5 @@ package org.openmrs.module.clientregistry.api; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.hl7.fhir.r4.model.Patient; import org.openmrs.module.fhir2.api.search.param.PatientSearchParams; @@ -8,7 +7,7 @@ public interface CRPatientService { - List getCRPatient(String sourceIdentifier, String sourceIdentifierSystem, List extraTargetSystems); + List getCRPatients(String sourceIdentifier, String sourceIdentifierSystem, List extraTargetSystems); List searchCRForPatients(PatientSearchParams patientSearchParams); } diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java b/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java index 7c19f52..05fe40d 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.rest.gclient.IQuery; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Patient; import org.openmrs.module.clientregistry.ClientRegistryConfig; import org.openmrs.module.clientregistry.api.CRPatientService; @@ -30,7 +31,16 @@ public class FhirCRPatientServiceImpl implements CRPatientService { * that match the given source identifier/system and target systems. */ @Override - public List getCRPatient(String sourceIdentifier, String sourceIdentifierSystem, List targetSystems) { + public List getCRPatients(String sourceIdentifier, String sourceIdentifierSystem, List targetSystems) { + + + Parameters betterCrRequest = fhirClient + .operation() + .onInstance(String.format("%s|%s", sourceIdentifierSystem, sourceIdentifier)) + .named(FhirCRConstants.IHE_PIX_OPERATION) + .withNoParameters(Parameters.class) + .execute(); + // construct and send request to external client registry IQuery crRequest = fhirClient .search() @@ -39,7 +49,7 @@ public List getCRPatient(String sourceIdentifier, String sourceIdentifi .where( FhirCRConstants.SOURCE_IDENTIFIER_PARAM.exactly().systemAndIdentifier(sourceIdentifierSystem, sourceIdentifier)); - + if (!targetSystems.isEmpty()) { crRequest.and(FhirCRConstants.TARGET_SYSTEM_PARAM.matches().values(targetSystems)); } diff --git a/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java b/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java index 72feea9..c88cad7 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/providers/r4/FhirCRPatientResourceProvider.java @@ -47,7 +47,7 @@ public Class getResourceType() { /** * FHIR endpoint to get Patient references from external client registry Example request: GET * [fhirbase - * ]/Patient/$ihe-pix?sourceIdentifier=1234{|sourceSystem}[&targetSystem=system1,system2] + * ]/Patient/$ihe-pix?sourceIdentifier={sourceSystem|}1234[&targetSystem=system1,system2] * * @param sourceIdentifierParam patient identifier token. If source system is included in token, * we will use it to override the module defined source system. @@ -81,7 +81,7 @@ public List getCRPatientById( "the sourceIdentifier request param"); } - List patients = clientRegistryManager.getPatientService().getCRPatient( + List patients = clientRegistryManager.getPatientService().getCRPatients( sourceIdentifierParam.getValue(), sourceIdentifierSystem, targetSystems ); From 41427b538be7e1f31ed39dfcc1f62f433feb5485 Mon Sep 17 00:00:00 2001 From: Pawel Mankowski Date: Mon, 13 Feb 2023 22:21:10 -0500 Subject: [PATCH 4/6] use onType --- .../clientregistry/api/impl/FhirCRPatientServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java b/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java index 05fe40d..c8f64ac 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java @@ -36,7 +36,7 @@ public List getCRPatients(String sourceIdentifier, String sourceIdentif Parameters betterCrRequest = fhirClient .operation() - .onInstance(String.format("%s|%s", sourceIdentifierSystem, sourceIdentifier)) + .onType(FhirConstants.PATIENT) .named(FhirCRConstants.IHE_PIX_OPERATION) .withNoParameters(Parameters.class) .execute(); From ff7e89e6eef5a6d05f4f573f06394561b10f9091 Mon Sep 17 00:00:00 2001 From: Pawel Mankowski Date: Sat, 18 Feb 2023 18:19:44 -0500 Subject: [PATCH 5/6] [HIE-2] make CR request for patients matching identifiers returned by ihe-pix CR endpoint --- .../api/impl/FhirCRPatientServiceImpl.java | 54 +++++++++++-------- .../providers/FhirCRConstants.java | 1 + 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java b/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java index c8f64ac..6f91773 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/impl/FhirCRPatientServiceImpl.java @@ -1,11 +1,15 @@ package org.openmrs.module.clientregistry.api.impl; import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInputAndPartialOutput; import ca.uhn.fhir.rest.gclient.IQuery; +import ca.uhn.fhir.rest.gclient.StringClientParam; +import ca.uhn.fhir.rest.param.StringOrListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.UriOrListParam; import org.hl7.fhir.instance.model.api.IBaseBundle; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Parameters; -import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.*; import org.openmrs.module.clientregistry.ClientRegistryConfig; import org.openmrs.module.clientregistry.api.CRPatientService; import org.openmrs.module.clientregistry.providers.FhirCRConstants; @@ -14,7 +18,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; @Component @@ -27,34 +33,40 @@ public class FhirCRPatientServiceImpl implements CRPatientService { private ClientRegistryConfig config; /** - * Constructs a $ihe-pix fhir client call to an external Client Registry returning any patients - * that match the given source identifier/system and target systems. + * Get patient identifiers from an external client registry's $ihe-pix implementation. Use the + * returned identifiers to then request a matching Patient bundle from the client registry. */ @Override public List getCRPatients(String sourceIdentifier, String sourceIdentifierSystem, List targetSystems) { - - - Parameters betterCrRequest = fhirClient + // construct request to external FHIR $ihe-pix endpoint + IOperationUntypedWithInputAndPartialOutput identifiersRequest = fhirClient .operation() .onType(FhirConstants.PATIENT) .named(FhirCRConstants.IHE_PIX_OPERATION) - .withNoParameters(Parameters.class) - .execute(); - - // construct and send request to external client registry - IQuery crRequest = fhirClient - .search() - .byUrl( - String.format("%s/%s", config.getClientRegistryServerUrl(), config.getClientRegistryGetPatientEndpoint())) - .where( - FhirCRConstants.SOURCE_IDENTIFIER_PARAM.exactly().systemAndIdentifier(sourceIdentifierSystem, - sourceIdentifier)); + .withSearchParameter(Parameters.class, FhirCRConstants.SOURCE_IDENTIFIER, new TokenParam(sourceIdentifierSystem, sourceIdentifier)); if (!targetSystems.isEmpty()) { - crRequest.and(FhirCRConstants.TARGET_SYSTEM_PARAM.matches().values(targetSystems)); + identifiersRequest.andSearchParameter(FhirCRConstants.TARGET_SYSTEM, new StringParam(String.join(",", targetSystems))); + } + + Parameters crMatchingParams = identifiersRequest.useHttpGet().execute(); + List crIdentifiers = crMatchingParams.getParameter().stream() + .filter(param -> Objects.equals(param.getName(), "targetId")) + .map(param -> param.getValue().toString()) + .collect(Collectors.toList()); + + if (crIdentifiers.isEmpty()) { + return Collections.emptyList(); } + + // construct and send request to external client registry + Bundle patientBundle = fhirClient + .search() + .forResource(Patient.class) + .where(new StringClientParam(Patient.SP_RES_ID).matches().values(crIdentifiers)) + .returnBundle(Bundle.class) + .execute(); - Bundle patientBundle = crRequest.returnBundle(Bundle.class).execute(); return parseCRPatientSearchResults(patientBundle); } diff --git a/api/src/main/java/org/openmrs/module/clientregistry/providers/FhirCRConstants.java b/api/src/main/java/org/openmrs/module/clientregistry/providers/FhirCRConstants.java index 3ec4eea..66c7286 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/providers/FhirCRConstants.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/providers/FhirCRConstants.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.rest.gclient.StringClientParam; import ca.uhn.fhir.rest.gclient.TokenClientParam; import ca.uhn.fhir.rest.gclient.UriClientParam; +import ca.uhn.fhir.rest.param.StringOrListParam; public class FhirCRConstants { From 7a17001aeb4347eac7305aee165d0d3e6b846949 Mon Sep 17 00:00:00 2001 From: samuelmale Date: Wed, 20 Mar 2024 01:57:49 +0300 Subject: [PATCH 6/6] Error handling --- .../api/event/PatientCreateUpdateListener.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/org/openmrs/module/clientregistry/api/event/PatientCreateUpdateListener.java b/api/src/main/java/org/openmrs/module/clientregistry/api/event/PatientCreateUpdateListener.java index 27973fd..bab5bce 100644 --- a/api/src/main/java/org/openmrs/module/clientregistry/api/event/PatientCreateUpdateListener.java +++ b/api/src/main/java/org/openmrs/module/clientregistry/api/event/PatientCreateUpdateListener.java @@ -5,6 +5,7 @@ import javax.jms.Message; import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -20,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; +import ca.uhn.fhir.parser.DataFormatException; @Component public class PatientCreateUpdateListener implements EventListener { @@ -98,7 +100,18 @@ private void processMessage(Message message) throws JMSException { if (mapMessage.getJMSDestination().toString().equals(ClientRegistryConstants.UPDATE_MESSAGE_DESTINATION)) { client.update().resource(patient).execute(); } else { - client.create().resource(patient).execute(); + try { + client.create().resource(patient).execute(); + } + catch (FhirClientConnectionException e) { + Throwable cause = e.getCause(); + if (cause instanceof DataFormatException) { + // just warn if the CR responds with unsupported data format + log.warn(e.getMessage()); + } else { + throw e; + } + } } } }