Skip to content

Commit

Permalink
FM2-649: Implementation of _has parameter for patients (#545)
Browse files Browse the repository at this point in the history
  • Loading branch information
icrc-jofrancisco authored Dec 3, 2024
1 parent c6ea1b3 commit 210dfa8
Show file tree
Hide file tree
Showing 12 changed files with 441 additions and 104 deletions.
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 @@ -364,4 +366,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,15 @@

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 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 @@ -32,18 +36,25 @@
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Projections;
import org.hibernate.sql.JoinType;
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 @@ -112,10 +123,57 @@ 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:
switch (hasParam.getParameterName()) {
case "id":
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) {
Criteria subquery = getSessionFactory().getCurrentSession().createCriteria(CohortMembership.class, "cm")
.createAlias("cm.cohort", "co").add(eq("co.uuid", groupId))
.setProjection(Projections.property("cm.patientId"));

return subquery.list();
}

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
Loading

0 comments on commit 210dfa8

Please sign in to comment.