Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) Implement Search API and consolidate the CR Patient Resource provider #18

Merged
merged 6 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is mainly because of https://profiles.ihe.net/ITI/PIXm/Parameters-pixm-response-mohralice-red-all.json.html which doesn't seem to have been correctly implemented in opencr hence this.


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
Loading