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

FM2-649: Implementation of _has parameter for patients #545

Merged
merged 5 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions api/src/main/java/org/openmrs/module/fhir2/FhirConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ private FhirConstants() {

public static final String MEDICATION = "Medication";

public static final String GROUP = "Group";

public static final String MEDICATION_DISPENSE = "MedicationDispense";

public static final String MEDICATION_REQUEST = "MedicationRequest";
Expand Down Expand Up @@ -356,4 +358,7 @@ private FhirConstants() {
public static final String EXACT_TOTAL_SEARCH_PARAMETER = "_exactTotal";

public static final String COUNT_QUERY_CACHE = "countQueryCache";

public static final String INCLUDE_MEMBER_PARAM = "member";

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
Expand All @@ -33,17 +38,24 @@
import org.hibernate.Criteria;
import org.hibernate.criterion.Criterion;
import org.hibernate.sql.JoinType;
import org.openmrs.Cohort;
import org.openmrs.CohortMembership;
import org.openmrs.Patient;
import org.openmrs.PatientIdentifierType;
import org.openmrs.module.fhir2.FhirConstants;
import org.openmrs.module.fhir2.api.dao.FhirGroupDao;
import org.openmrs.module.fhir2.api.dao.FhirPatientDao;
import org.openmrs.module.fhir2.api.search.param.SearchParameterMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@Setter(AccessLevel.PACKAGE)
public class FhirPatientDaoImpl extends BasePersonDao<Patient> implements FhirPatientDao {

@Autowired
private FhirGroupDao groupDao;

@Override
public Patient getPatientById(@Nonnull Integer id) {
return (Patient) getSessionFactory().getCurrentSession().createCriteria(Patient.class).add(eq("patientId", id))
Expand Down Expand Up @@ -111,10 +123,55 @@ protected void setupSearchParams(Criteria criteria, SearchParameterMap theParams
case FhirConstants.COMMON_SEARCH_HANDLER:
handleCommonSearchParameters(entry.getValue()).ifPresent(criteria::add);
break;
case FhirConstants.HAS_SEARCH_HANDLER:
entry.getValue().forEach(param -> handleHasAndListParam(criteria, (HasAndListParam) param.getParam()));
break;
}
});
}

protected void handleHasAndListParam(Criteria criteria, HasAndListParam hasAndListParam) {
if (hasAndListParam != null) {
List<String> groupIds = new ArrayList<>();
hasAndListParam.getValuesAsQueryTokens().forEach(hasOrListParam -> {
hasOrListParam.getValuesAsQueryTokens().forEach(hasParam -> {
if (hasParam != null) {
String paramValue = hasParam.getParameterValue();
switch (hasParam.getTargetResourceType()) {
case FhirConstants.GROUP:
switch (hasParam.getReferenceFieldName()) {
case FhirConstants.INCLUDE_MEMBER_PARAM:
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we also have some sort of check for the :id part mentioned in the PR comments?

groupIds.add(paramValue);
break;
}
break;
}
}
});
});

if (!groupIds.isEmpty()) {
verifyPatientInGroups(criteria, groupIds);
}
}
}

private void verifyPatientInGroups(Criteria criteria, List<String> groupIds) {
Set<Integer> patientIds = new HashSet<>();
groupIds.forEach(groupId -> patientIds.addAll(getGroupMemberIds(groupId)));

criteria.add(in("patientId", patientIds.isEmpty() ? Collections.emptyList() : patientIds));
}

private List<Integer> getGroupMemberIds(String groupId) {
Cohort cohort = groupDao.get(groupId);
if (cohort != null) {
return cohort.getMemberships().stream().map(CohortMembership::getPatientId).collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Can we reformulate this as a subquery?


private void handlePatientQuery(Criteria criteria, @Nonnull StringAndListParam query) {
if (query == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import lombok.Builder;
Expand Down Expand Up @@ -45,11 +46,13 @@ public class OpenmrsPatientSearchParams extends BaseResourceSearchParams {

private StringAndListParam country;

private HasAndListParam hasAndListParam;

@Builder
public OpenmrsPatientSearchParams(StringAndListParam query, TokenAndListParam gender, DateRangeParam birthDate,
DateRangeParam deathDate, TokenAndListParam deceased, StringAndListParam city, StringAndListParam state,
StringAndListParam postalCode, StringAndListParam country, TokenAndListParam id, DateRangeParam lastUpdated,
SortSpec sort, HashSet<Include> revIncludes) {
StringAndListParam postalCode, StringAndListParam country, TokenAndListParam id, HasAndListParam hasAndListParam,
DateRangeParam lastUpdated, SortSpec sort, HashSet<Include> revIncludes) {

super(id, lastUpdated, sort, null, revIncludes);

Expand All @@ -62,6 +65,7 @@ public OpenmrsPatientSearchParams(StringAndListParam query, TokenAndListParam ge
this.state = state;
this.postalCode = postalCode;
this.country = country;
this.hasAndListParam = hasAndListParam;
}

@Override
Expand All @@ -74,6 +78,7 @@ public SearchParameterMap toSearchParameterMap() {
.addParameter(FhirConstants.ADDRESS_SEARCH_HANDLER, FhirConstants.CITY_PROPERTY, getCity())
.addParameter(FhirConstants.ADDRESS_SEARCH_HANDLER, FhirConstants.STATE_PROPERTY, getState())
.addParameter(FhirConstants.ADDRESS_SEARCH_HANDLER, FhirConstants.POSTAL_CODE_PROPERTY, getPostalCode())
.addParameter(FhirConstants.ADDRESS_SEARCH_HANDLER, FhirConstants.COUNTRY_PROPERTY, getCountry());
.addParameter(FhirConstants.ADDRESS_SEARCH_HANDLER, FhirConstants.COUNTRY_PROPERTY, getCountry())
.addParameter(FhirConstants.HAS_SEARCH_HANDLER, getHasAndListParam());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import lombok.Builder;
Expand Down Expand Up @@ -51,12 +52,14 @@ public class PatientSearchParams extends BaseResourceSearchParams {

private StringAndListParam country;

private HasAndListParam hasAndListParam;

@Builder
public PatientSearchParams(StringAndListParam name, StringAndListParam given, StringAndListParam family,
TokenAndListParam identifier, TokenAndListParam gender, DateRangeParam birthDate, DateRangeParam deathDate,
TokenAndListParam deceased, StringAndListParam city, StringAndListParam state, StringAndListParam postalCode,
StringAndListParam country, TokenAndListParam id, DateRangeParam lastUpdated, SortSpec sort,
HashSet<Include> revIncludes) {
StringAndListParam country, TokenAndListParam id, HasAndListParam hasAndListParam, DateRangeParam lastUpdated,
SortSpec sort, HashSet<Include> revIncludes) {

super(id, lastUpdated, sort, null, revIncludes);

Expand All @@ -72,6 +75,7 @@ public PatientSearchParams(StringAndListParam name, StringAndListParam given, St
this.state = state;
this.postalCode = postalCode;
this.country = country;
this.hasAndListParam = hasAndListParam;
}

@Override
Expand All @@ -88,6 +92,7 @@ public SearchParameterMap toSearchParameterMap() {
.addParameter(FhirConstants.ADDRESS_SEARCH_HANDLER, FhirConstants.CITY_PROPERTY, getCity())
.addParameter(FhirConstants.ADDRESS_SEARCH_HANDLER, FhirConstants.STATE_PROPERTY, getState())
.addParameter(FhirConstants.ADDRESS_SEARCH_HANDLER, FhirConstants.POSTAL_CODE_PROPERTY, getPostalCode())
.addParameter(FhirConstants.ADDRESS_SEARCH_HANDLER, FhirConstants.COUNTRY_PROPERTY, getCountry());
.addParameter(FhirConstants.ADDRESS_SEARCH_HANDLER, FhirConstants.COUNTRY_PROPERTY, getCountry())
.addParameter(FhirConstants.HAS_SEARCH_HANDLER, getHasAndListParam());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam;
Expand All @@ -53,6 +54,7 @@
import org.hl7.fhir.dstu3.model.ProcedureRequest;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.ServiceRequest;
import org.openmrs.module.fhir2.FhirConstants;
import org.openmrs.module.fhir2.api.FhirPatientService;
import org.openmrs.module.fhir2.api.annotations.R3Provider;
import org.openmrs.module.fhir2.api.search.SearchQueryBundleProviderR3Wrapper;
Expand Down Expand Up @@ -128,6 +130,7 @@ public IBundleProvider searchPatients(@OptionalParam(name = Patient.SP_NAME) Str
@OptionalParam(name = Patient.SP_ADDRESS_POSTALCODE) StringAndListParam postalCode,
@OptionalParam(name = Patient.SP_ADDRESS_COUNTRY) StringAndListParam country,
@OptionalParam(name = Patient.SP_RES_ID) TokenAndListParam id,
@OptionalParam(name = FhirConstants.HAS_SEARCH_HANDLER) HasAndListParam hasAndListParam,
@OptionalParam(name = "_lastUpdated") DateRangeParam lastUpdated, @Sort SortSpec sort,
@IncludeParam(reverse = true, allow = { "Observation:" + Observation.SP_PATIENT,
"AllergyIntolerance:" + AllergyIntolerance.SP_PATIENT, "DiagnosticReport:" + DiagnosticReport.SP_PATIENT,
Expand All @@ -138,9 +141,9 @@ public IBundleProvider searchPatients(@OptionalParam(name = Patient.SP_NAME) Str
revIncludes = null;
}

return new SearchQueryBundleProviderR3Wrapper(
patientService.searchForPatients(new PatientSearchParams(name, given, family, identifier, gender, birthDate,
deathDate, deceased, city, state, postalCode, country, id, lastUpdated, sort, revIncludes)));
return new SearchQueryBundleProviderR3Wrapper(patientService
.searchForPatients(new PatientSearchParams(name, given, family, identifier, gender, birthDate, deathDate,
deceased, city, state, postalCode, country, id, hasAndListParam, lastUpdated, sort, revIncludes)));
}

@Search(queryName = "openmrsPatients")
Expand All @@ -155,6 +158,7 @@ public IBundleProvider searchOpenmrsPatients(@OptionalParam(name = "q") StringAn
@OptionalParam(name = org.hl7.fhir.r4.model.Patient.SP_ADDRESS_POSTALCODE) StringAndListParam postalCode,
@OptionalParam(name = org.hl7.fhir.r4.model.Patient.SP_ADDRESS_COUNTRY) StringAndListParam country,
@OptionalParam(name = org.hl7.fhir.r4.model.Patient.SP_RES_ID) TokenAndListParam id,
@OptionalParam(name = FhirConstants.HAS_SEARCH_HANDLER) HasAndListParam hasAndListParam,
@OptionalParam(name = "_lastUpdated") DateRangeParam lastUpdated, @Sort SortSpec sort,
@IncludeParam(reverse = true, allow = { "Observation:" + org.hl7.fhir.r4.model.Observation.SP_PATIENT,
"AllergyIntolerance:" + org.hl7.fhir.r4.model.AllergyIntolerance.SP_PATIENT,
Expand All @@ -169,7 +173,7 @@ public IBundleProvider searchOpenmrsPatients(@OptionalParam(name = "q") StringAn

return new SearchQueryBundleProviderR3Wrapper(
patientService.searchForPatients(new OpenmrsPatientSearchParams(query, gender, birthDate, deathDate,
deceased, city, state, postalCode, country, id, lastUpdated, sort, revIncludes)));
deceased, city, state, postalCode, country, id, hasAndListParam, lastUpdated, sort, revIncludes)));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam;
Expand All @@ -54,6 +55,7 @@
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.ServiceRequest;
import org.openmrs.module.fhir2.FhirConstants;
import org.openmrs.module.fhir2.api.FhirPatientService;
import org.openmrs.module.fhir2.api.annotations.R4Provider;
import org.openmrs.module.fhir2.api.search.param.OpenmrsPatientSearchParams;
Expand Down Expand Up @@ -136,6 +138,7 @@ public IBundleProvider searchPatients(@OptionalParam(name = Patient.SP_NAME) Str
@OptionalParam(name = Patient.SP_ADDRESS_POSTALCODE) StringAndListParam postalCode,
@OptionalParam(name = Patient.SP_ADDRESS_COUNTRY) StringAndListParam country,
@OptionalParam(name = Patient.SP_RES_ID) TokenAndListParam id,
@OptionalParam(name = FhirConstants.HAS_SEARCH_HANDLER) HasAndListParam hasAndListParam,
@OptionalParam(name = "_lastUpdated") DateRangeParam lastUpdated, @Sort SortSpec sort,
@IncludeParam(reverse = true, allow = { "Observation:" + Observation.SP_PATIENT,
"AllergyIntolerance:" + AllergyIntolerance.SP_PATIENT, "DiagnosticReport:" + DiagnosticReport.SP_PATIENT,
Expand All @@ -147,7 +150,7 @@ public IBundleProvider searchPatients(@OptionalParam(name = Patient.SP_NAME) Str
}

return patientService.searchForPatients(new PatientSearchParams(name, given, family, identifier, gender, birthDate,
deathDate, deceased, city, state, postalCode, country, id, lastUpdated, sort, revIncludes));
deathDate, deceased, city, state, postalCode, country, id, hasAndListParam, lastUpdated, sort, revIncludes));
}

@Search(queryName = "openmrsPatients")
Expand All @@ -162,6 +165,7 @@ public IBundleProvider searchOpenmrsPatients(@OptionalParam(name = "q") StringAn
@OptionalParam(name = Patient.SP_ADDRESS_POSTALCODE) StringAndListParam postalCode,
@OptionalParam(name = Patient.SP_ADDRESS_COUNTRY) StringAndListParam country,
@OptionalParam(name = Patient.SP_RES_ID) TokenAndListParam id,
@OptionalParam(name = FhirConstants.HAS_SEARCH_HANDLER) HasAndListParam hasAndListParam,
@OptionalParam(name = "_lastUpdated") DateRangeParam lastUpdated, @Sort SortSpec sort,
@IncludeParam(reverse = true, allow = { "Observation:" + Observation.SP_PATIENT,
"AllergyIntolerance:" + AllergyIntolerance.SP_PATIENT, "DiagnosticReport:" + DiagnosticReport.SP_PATIENT,
Expand All @@ -173,7 +177,7 @@ public IBundleProvider searchOpenmrsPatients(@OptionalParam(name = "q") StringAn
}

return patientService.searchForPatients(new OpenmrsPatientSearchParams(query, gender, birthDate, deathDate, deceased,
city, state, postalCode, country, id, lastUpdated, sort, revIncludes));
city, state, postalCode, country, id, hasAndListParam, lastUpdated, sort, revIncludes));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,23 @@
package org.openmrs.module.fhir2.api.dao.impl;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;

import java.util.List;

import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.HasOrListParam;
import ca.uhn.fhir.rest.param.HasParam;
import org.hibernate.SessionFactory;
import org.junit.Before;
import org.junit.Test;
import org.openmrs.Patient;
import org.openmrs.module.fhir2.FhirConstants;
import org.openmrs.module.fhir2.TestFhirSpringConfiguration;
import org.openmrs.module.fhir2.api.search.param.SearchParameterMap;
import org.openmrs.test.BaseModuleContextSensitiveTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
Expand All @@ -30,19 +38,30 @@ public class FhirPatientDaoImplTest extends BaseModuleContextSensitiveTest {

private static final String BAD_PATIENT_UUID = "282390a6-3608-496d-9025-aecbc1235670";

private static final String GROUP_A = "dfb29c44-2e39-46c4-8cd7-18f21c6d47b1";

private static final String GROUP_B = "a25ce1d7-326c-43ff-a87f-63d9d2f60f11";

private static final String PATIENT_GROUP_A = "61b38324-e2fd-4feb-95b7-9e9a2a4400df";

private static final String[] PATIENT_SEARCH_DATA_FILES = {
"org/openmrs/module/fhir2/api/dao/impl/FhirPatientDaoImplTest_initial_data.xml",
"org/openmrs/module/fhir2/api/dao/impl/FhirPatientDaoImplTest_address_data.xml" };

private FhirPatientDaoImpl dao;

private FhirGroupDaoImpl groupDao;

@Autowired
private SessionFactory sessionFactory;

@Before
public void setup() throws Exception {
groupDao = new FhirGroupDaoImpl();
groupDao.setSessionFactory(sessionFactory);
dao = new FhirPatientDaoImpl();
dao.setSessionFactory(sessionFactory);
dao.setGroupDao(groupDao);
for (String search_data : PATIENT_SEARCH_DATA_FILES) {
executeDataSet(search_data);
}
Expand Down Expand Up @@ -76,4 +95,28 @@ public void getPatientByUuid_shouldReturnNullIfPatientNotFound() {

assertThat(result, nullValue());
}

@Test
public void getSearchResults_shouldReturnPatientSearchResults() {
HasAndListParam groupParam = new HasAndListParam().addAnd(
new HasOrListParam().add(new HasParam(FhirConstants.GROUP, FhirConstants.INCLUDE_MEMBER_PARAM, "id", GROUP_A)));

SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.HAS_SEARCH_HANDLER, groupParam);
List<Patient> result = dao.getSearchResults(theParams);

assertThat(result, notNullValue());
assertThat(result.get(0).getUuid(), equalTo(PATIENT_GROUP_A));
}

@Test
public void getSearchResults_shouldReturnEmptyList() {
HasAndListParam groupParam = new HasAndListParam().addAnd(
new HasOrListParam().add(new HasParam(FhirConstants.GROUP, FhirConstants.INCLUDE_MEMBER_PARAM, "id", GROUP_B)));

SearchParameterMap theParams = new SearchParameterMap().addParameter(FhirConstants.HAS_SEARCH_HANDLER, groupParam);
List<Patient> result = dao.getSearchResults(theParams);

assertThat(result, notNullValue());
assertThat(result, empty());
}
}
Loading