Skip to content

Commit

Permalink
[HIE-2] client registry Patient search flow implementation
Browse files Browse the repository at this point in the history
[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
  • Loading branch information
pmanko2 committed Feb 12, 2023
1 parent 24f602a commit e14b7ae
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.stereotype.Component;
Expand All @@ -34,6 +35,20 @@ public String getClientRegistryServerUrl() {
return administrationService.getGlobalProperty(ClientRegistryConstants.GP_CLIENT_REGISTRY_SERVER_URL);
}

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 administrationService.getGlobalProperty(ClientRegistryConstants.GP_CLIENT_REGISTRY_USER_NAME);
}
Expand All @@ -45,4 +60,8 @@ public String getClientRegistryPassword() {
public String getClientRegistryIdentifierRoot() {
return administrationService.getGlobalProperty(ClientRegistryConstants.GP_CLIENT_REGISTRY_IDENTIFIER_ROOT);
}

public String getClientRegistryTransactionMethod() {
return administrationService.getGlobalProperty(ClientRegistryConstants.GP_CLIENT_REGISTRY_TRANSACTION_METHOD);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@

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";

public static final String GP_CLIENT_REGISTRY_PASSWORD = "clientregistry.password";

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";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.openmrs.module.clientregistry;

public enum ClientRegistryTransactionType {
FHIR,
HL7
}
Original file line number Diff line number Diff line change
@@ -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<Patient> getCRPatient(String sourceIdentifier, String sourceIdentifierSystem, List<String> extraTargetSystems);

List<Patient> searchCRForPatients(PatientSearchParams patientSearchParams);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}
Expand Down Expand Up @@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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<Patient> getCRPatient(String sourceIdentifier, String sourceIdentifierSystem, List<String> extraTargetSystems) {
// construct and send request to external client registry
IQuery<IBaseBundle> 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<Patient> searchCRForPatients(PatientSearchParams patientSearchParams) {
return null;
}

/**
* Filter and parse out fhir patients from Client Registry Patient Search results
*/
private List<Patient> parseCRPatientSearchResults(Bundle patientBundle) {
return patientBundle
.getEntry()
.stream()
.filter(entry -> entry.hasType(FhirConstants.PATIENT))
.map(entry -> (Patient) entry.getResource())
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
Original file line number Diff line number Diff line change
@@ -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<? extends IBaseResource> 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<Patient> 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<String> 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<Patient> patients = clientRegistryManager.getPatientService().getCRPatient(
sourceIdentifierParam.getValue(), sourceIdentifierSystem, targetSystems
);

if (patients.isEmpty()) {
throw new ResourceNotFoundException("No Client Registry patients found.");
}

return patients;
}

@Search
public List<Patient> searchClientRegistryPatients() {
throw new NotImplementedOperationException("search client registry is not yet implemented");
}
}
28 changes: 26 additions & 2 deletions omod/src/main/resources/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,37 @@

<!-- Required Global Properties -->
<globalProperty>
<property>@MODULE_ID@.serverUrl</property>
<defaultValue>http://localhost:5001/CR/fhir/</defaultValue>
<property>@MODULE_ID@.clientRegistryServerUrl</property>
<defaultValue>http://localhost:5001/CR/fhir</defaultValue>
<description>
Base URL for the Client Registry Server
</description>
</globalProperty>

<globalProperty>
<property>@[email protected]</property>
<defaultValue>Patient/$ihe-pix</defaultValue>
<description>
Client registry endpoint implementing the Patient identifier cross-reference transaction (ITI-83)
</description>
</globalProperty>

<globalProperty>
<property>@[email protected]</property>
<defaultValue></defaultValue>
<description>
Default system from which the Patient identifiers will be returned, if no system provided in requests
</description>
</globalProperty>

<globalProperty>
<property>@[email protected]</property>
<defaultValue>fhir</defaultValue>
<description>
Transaction method supported by the Client Registry. Currently supporting fhir or hl7.
</description>
</globalProperty>

<globalProperty>
<property>@[email protected]</property>
<defaultValue>openmrs</defaultValue>
Expand Down
5 changes: 3 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
<dependency>
<groupId>org.openmrs.tools</groupId>
<artifactId>openmrs-tools</artifactId>
<version>${openmrsPlatformVersion}</version>
<version>${openmrsPlatformToolsVersion}</version>
</dependency>
</dependencies>
<executions>
Expand Down Expand Up @@ -217,7 +217,8 @@

<properties>
<revision>1.1.5</revision>
<fhir2Version>1.5.1</fhir2Version>
<fhir2Version>1.8.0</fhir2Version>
<openmrsPlatformVersion>1.11.6</openmrsPlatformVersion>
<openmrsPlatformToolsVersion>2.4.0</openmrsPlatformToolsVersion>
</properties>
</project>

0 comments on commit e14b7ae

Please sign in to comment.