Skip to content

Commit

Permalink
Merge pull request #18 from openmrs/feat/crProvider
Browse files Browse the repository at this point in the history
(feat) Implement Search API and consolidate the CR Patient Resource provider
  • Loading branch information
samuelmale authored Sep 23, 2024
2 parents 08a10e2 + f3ef268 commit f065f1d
Show file tree
Hide file tree
Showing 11 changed files with 1,017 additions and 100 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
* OpenMRS is also distributed under the terms of the Healthcare Disclaimer located at
* http://openmrs.org/license. Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the
* OpenMRS graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.module.clientregistry;

Expand All @@ -28,16 +27,16 @@ public class ClientRegistryConfig {
@Qualifier("adminService")
AdministrationService administrationService;

@Value("${CLIENTREGISTRY_SERVERURL}")
@Value("${CLIENTREGISTRY_SERVERURL:}")
private String serverUrl;

@Value("${CLIENTREGISTRY_USERNAME}")
@Value("${CLIENTREGISTRY_USERNAME:}")
private String username;

@Value("${CLIENTREGISTRY_PASSWORD}")
@Value("${CLIENTREGISTRY_PASSWORD:}")
private String password;

@Value("${CLIENTREGISTRY_IDENTIFIERROOT}")
@Value("${CLIENTREGISTRY_IDENTIFIERROOT:}")
private String identifierRoot;

public boolean clientRegistryConnectionEnabled() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,13 @@ public class ClientRegistryConstants {
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 CR_FHIR_OPERATION = "$cr";

public static final String CR_FHIR_SEARCH_OPERATION = "$cr-search";

public static final String CR_FHIR_UPDATE_OPERATION = "$cr-update";

public static final String CR_FHIR_DELETE_OPERATION = "$cr-delete";

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,54 @@

import org.hl7.fhir.r4.model.Patient;
import org.openmrs.module.fhir2.api.search.param.PatientSearchParams;

import ca.uhn.fhir.rest.api.server.IBundleProvider;
import java.util.List;

public interface CRPatientService {

List<Patient> getCRPatients(String sourceIdentifier, String sourceIdentifierSystem, List<String> extraTargetSystems);
/**
* Queries patient by ID
*/
Patient getPatientById(String id);

/**
* Queries patients through a PIXm manager using native IDs.
*
* @param sourceIdentifier the source identifier of the patient
* @param sourceIdentifierSystem the system of the source identifier
* @param extraTargetSystems additional target systems
* @return a bundle containing patients that match the specified identifiers
*/
IBundleProvider getPatientsByPIX(String sourceIdentifier, String sourceIdentifierSystem, List<String> extraTargetSystems);

/**
* Searches for patients, including fuzzy search.
*
* @param patientSearchParams the parameters for searching patients
* @return a bundle containing patients that match the search criteria
*/
IBundleProvider searchPatients(PatientSearchParams patientSearchParams);

/**
* Creates a patient record
*
* @param patient the patient to create
* @return the created patient
*/
Patient createPatient(Patient patient);

/**
* Updates a patient record.
*
* @param patient the patient to update
* @return the updated patient
*/
Patient updatePatient(Patient patient);

List<Patient> searchCRForPatients(PatientSearchParams patientSearchParams);
/**
* Purges a patient record from the registry.
*
* @param patient the patient to purge
*/
void purgePatient(Patient patient);
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
package org.openmrs.module.clientregistry.api.impl;

import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.gclient.ICriterion;
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.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.r4.model.*;
import org.openmrs.module.clientregistry.ClientRegistryConfig;
import org.openmrs.module.clientregistry.api.CRPatientService;
import org.openmrs.module.clientregistry.api.search.CRSearchBundleProvider;
import org.openmrs.module.clientregistry.api.search.PatientSearchCriteriaBuilder;
import org.openmrs.module.clientregistry.providers.FhirCRConstants;
import org.openmrs.module.fhir2.FhirConstants;
import org.openmrs.module.fhir2.api.FhirGlobalPropertyService;
import org.openmrs.module.fhir2.api.search.param.PatientSearchParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Qualifier;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
Expand All @@ -31,58 +33,86 @@ public class FhirCRPatientServiceImpl implements CRPatientService {
@Qualifier("clientRegistryFhirClient")
private IGenericClient fhirClient;

/**
* 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.
*/
@Autowired
private PatientSearchCriteriaBuilder criteriaBuilder;

@Autowired
private FhirGlobalPropertyService globalPropertyService;

@Override
public Patient getPatientById(String id) {
if (StringUtils.isBlank(id)) {
return null;
}
return fhirClient.read().resource(Patient.class).withId(id).execute();
}

@Override
public List<Patient> getCRPatients(String sourceIdentifier, String sourceIdentifierSystem, List<String> targetSystems) {
public IBundleProvider getPatientsByPIX(String sourceIdentifier, String sourceIdentifierSystem,
List<String> targetSystems) {
// construct request to external FHIR $ihe-pix endpoint
IOperationUntypedWithInputAndPartialOutput<Parameters> identifiersRequest = fhirClient
.operation()
.onType(FhirConstants.PATIENT)
.named(FhirCRConstants.IHE_PIX_OPERATION)
.withSearchParameter(Parameters.class, FhirCRConstants.SOURCE_IDENTIFIER, new TokenParam(sourceIdentifierSystem, sourceIdentifier));

IOperationUntypedWithInputAndPartialOutput<Parameters> identifiersRequest = fhirClient.operation()
.onType(FhirConstants.PATIENT).named(FhirCRConstants.IHE_PIX_OPERATION).withSearchParameter(Parameters.class,
FhirCRConstants.SOURCE_IDENTIFIER, new TokenParam(sourceIdentifierSystem, sourceIdentifier));

if (!targetSystems.isEmpty()) {
identifiersRequest.andSearchParameter(FhirCRConstants.TARGET_SYSTEM, new StringParam(String.join(",", targetSystems)));
identifiersRequest.andSearchParameter(FhirCRConstants.TARGET_SYSTEM,
new StringParam(String.join(",", targetSystems)));
}

Parameters crMatchingParams = identifiersRequest.useHttpGet().execute();
List<String> crIdentifiers = crMatchingParams.getParameter().stream()
.filter(param -> Objects.equals(param.getName(), "targetId"))
.map(param -> param.getValue().toString())
.collect(Collectors.toList());

List<String> crIdentifiers = crMatchingParams.getParameter().stream().filter(param -> Objects.equals(param.getName(), "targetId"))
.map(param -> ((Reference) param.getValue()).getReference()).collect(Collectors.toList());

if (crIdentifiers.isEmpty()) {
return Collections.emptyList();
return new CRSearchBundleProvider(Collections.emptyList(), globalPropertyService);
}

// 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();

return parseCRPatientSearchResults(patientBundle);
Bundle patientBundle = fhirClient.search().forResource(Patient.class)
.where(new StringClientParam(Patient.SP_RES_ID).matches().values(crIdentifiers)).returnBundle(Bundle.class)
.execute();

return new CRSearchBundleProvider(parseCRPatientSearchResults(patientBundle), globalPropertyService);

}

@Override
public IBundleProvider searchPatients(PatientSearchParams patientSearchParams) {
List<ICriterion<?>> criterions = criteriaBuilder.buildCriteria(patientSearchParams);
IQuery<IBaseBundle> query = fhirClient.search().forResource(Patient.class);

for (int i = 0; i < criterions.size(); i++) {
ICriterion<?> criterion = criterions.get(i);
if (i == 0) {
query.where(criterion);
} else {
query.and(criterion);
}
}
Bundle patientBundle = query.returnBundle(Bundle.class).execute();
return new CRSearchBundleProvider(parseCRPatientSearchResults(patientBundle), globalPropertyService);
}

@Override
public Patient createPatient(Patient patient) {
return (Patient) fhirClient.create().resource(patient).execute().getResource();
}

@Override
public List<Patient> searchCRForPatients(PatientSearchParams patientSearchParams) {
return null;
public Patient updatePatient(Patient patient) {
return (Patient) fhirClient.update().resource(patient).execute().getResource();
}

@Override
public void purgePatient(Patient patient) {
fhirClient.delete().resource(patient).execute();
}

/**
* 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());
}
return patientBundle.getEntry().stream().filter(entry -> entry.getResource().hasType(FhirConstants.PATIENT))
.map(entry -> (Patient) entry.getResource()).collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.openmrs.module.clientregistry.api.search;

import java.io.Serializable;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.openmrs.module.fhir2.FhirConstants;
import org.openmrs.module.fhir2.api.FhirGlobalPropertyService;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;

public class CRSearchBundleProvider extends SimpleBundleProvider implements Serializable {

private final FhirGlobalPropertyService globalPropertyService;

public CRSearchBundleProvider(List<? extends IBaseResource> patientList, FhirGlobalPropertyService globalPropertyService) {
super(patientList);
this.globalPropertyService = globalPropertyService;
}

@Override
public Integer preferredPageSize() {
if (size() == null) {
setSize(globalPropertyService.getGlobalProperty(FhirConstants.OPENMRS_FHIR_DEFAULT_PAGE_SIZE, 10));
}
return size();
}

}
Loading

0 comments on commit f065f1d

Please sign in to comment.