Skip to content

Commit

Permalink
FM2-347:Add support for _has and _has:...:not for ServiceRequest and …
Browse files Browse the repository at this point in the history
…Observation
  • Loading branch information
gitcliff committed Jul 7, 2021
1 parent 6abb746 commit 3676c0e
Show file tree
Hide file tree
Showing 28 changed files with 735 additions and 295 deletions.
2 changes: 2 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 @@ -303,6 +303,8 @@ private FhirConstants() {
public static final String CONDITION_OBSERVATION_CONCEPT_UUID = "1284AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";

public static final String MAX_SEARCH_HANDLER = "max.search.handler";

public static final String HAS_PROPERTY = "_has";

public static final String LASTN_OBSERVATION_SEARCH_HANDLER = "lastn.observation.search.handler";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public interface FhirObservationService extends FhirService<Observation> {
Observation get(@Nonnull String uuid);

IBundleProvider searchForObservations(ReferenceAndListParam encounterReference, ReferenceAndListParam patientReference,
ReferenceAndListParam hasMemberReference, TokenAndListParam valueConcept, DateRangeParam valueDateParam,
ReferenceAndListParam hasMemberReference,ReferenceAndListParam basedOnReference, TokenAndListParam valueConcept, DateRangeParam valueDateParam,
QuantityAndListParam valueQuantityParam, StringAndListParam valueStringParam, DateRangeParam date,
TokenAndListParam code, TokenAndListParam category, TokenAndListParam id, DateRangeParam lastUpdated,
SortSpec sort, HashSet<Include> includes, HashSet<Include> revIncludes);
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.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import org.hl7.fhir.r4.model.ServiceRequest;
Expand All @@ -22,5 +23,5 @@ public interface FhirServiceRequestService extends FhirService<ServiceRequest> {

IBundleProvider searchForServiceRequests(ReferenceAndListParam patientReference, TokenAndListParam code,
ReferenceAndListParam encounterReference, ReferenceAndListParam participantReference, DateRangeParam occurrence,
TokenAndListParam uuid, DateRangeParam lastUpdated, HashSet<Include> includes);
TokenAndListParam uuid, DateRangeParam lastUpdated,HasAndListParam has, HashSet<Include> includes);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@

import javax.annotation.Nonnull;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -71,6 +74,8 @@
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.time.DateUtils;
Expand Down Expand Up @@ -164,9 +169,24 @@
* }</pre>
* </p>
*/
@Slf4j
public abstract class BaseDao {

private static final BigDecimal APPROX_RANGE = new BigDecimal("0.1");

private static final MethodHandle detachedCriteria_getCriteriaImpl;

static {
MethodHandle temporaryHandle;
try {
temporaryHandle = MethodHandles.lookup().findGetter(DetachedCriteria.class, "impl", CriteriaImpl.class);
}
catch (NoSuchFieldException | IllegalAccessException e) {
log.error("Could not find method handle to get impl field from DetachedCriteria", e);
temporaryHandle = null;
}
detachedCriteria_getCriteriaImpl = temporaryHandle;
}

@Autowired
private LocalDateTimeFactory localDateTimeFactory;
Expand Down Expand Up @@ -230,6 +250,20 @@ protected boolean lacksAlias(@Nonnull Criteria criteria, @Nonnull String alias)

return subcriteria.filter(subcriteriaIterator -> containsAlias(subcriteriaIterator, alias)).isPresent();
}

/**
* Determines whether or not the given criteria object already has a given alias. This is useful to
* determine whether a mapping has already been made or whether a given alias is already in use.
*
* @param criteria the {@link DetachedCriteria} object to examine
* @param alias the alias to look for
* @return true if the alias exists in this criteria object, false otherwise
*/
protected boolean lacksAlias(@Nonnull DetachedCriteria criteria, @Nonnull String alias) {
Optional<Iterator<CriteriaImpl.Subcriteria>> subcriteria = asImpl(criteria).map(CriteriaImpl::iterateSubcriteria);

return subcriteria.filter(subcriteriaIterator -> containsAlias(subcriteriaIterator, alias)).isPresent();
}

/**
* Determines whether any of the {@link CriteriaImpl.Subcriteria} objects returned by a given
Expand Down Expand Up @@ -1007,6 +1041,15 @@ protected List<String> tokensToList(List<TokenParam> tokens) {
protected Stream<String> tokensToParams(List<TokenParam> tokens) {
return tokens.stream().map(TokenParam::getValue);
}

@SneakyThrows
protected Optional<CriteriaImpl> asImpl(DetachedCriteria criteria) {
if (detachedCriteria_getCriteriaImpl == null) {
return Optional.empty();
}
return asImpl((CriteriaImpl) detachedCriteria_getCriteriaImpl.invokeExact(criteria));
}


private String groupBySystem(@Nonnull TokenParam token) {
return StringUtils.trimToEmpty(token.getSystem());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ protected void setupSearchParams(Criteria criteria, SearchParameterMap theParams
case FhirConstants.CODED_SEARCH_HANDLER:
entry.getValue().forEach(code -> handleCodedConcept(criteria, (TokenAndListParam) code.getParam()));
break;
case FhirConstants.BASED_ON_REFERENCE_SEARCH_HANDLER:
entry.getValue().forEach(param -> handleReference("ord", (ReferenceAndListParam) param.getParam())
.ifPresent(c -> criteria.createAlias("order", "ord").add(c)));
break;
case FhirConstants.CATEGORY_SEARCH_HANDLER:
entry.getValue()
.forEach(category -> handleConceptClass(criteria, (TokenAndListParam) category.getParam()));
Expand Down Expand Up @@ -170,6 +174,14 @@ private void handleHasMemberReference(Criteria criteria, ReferenceAndListParam h
}).ifPresent(criteria::add);
}
}

private Optional<Criterion> handleReference(@Nonnull String orderAlias, ReferenceAndListParam orderReference) {
if (orderReference == null) {
return Optional.empty();
}
return handleAndListParam(orderReference,
param -> Optional.of(eq(String.format("%s.uuid", orderAlias), param.getIdPart())));
}

private Optional<Criterion> handleValueStringParam(@Nonnull String propertyName, StringAndListParam valueStringParam) {
return handleAndListParam(valueStringParam, v -> propertyLike(propertyName, v.getValue()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,42 @@
*/
package org.openmrs.module.fhir2.api.dao.impl;

import static org.hibernate.criterion.Projections.property;
import static org.hibernate.criterion.Restrictions.and;
import static org.hibernate.criterion.Restrictions.or;

import java.util.Optional;
import java.util.stream.Stream;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import lombok.AccessLevel;
import lombok.Setter;
import org.hibernate.Criteria;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Subqueries;
import org.hl7.fhir.r4.model.Observation;
import org.openmrs.Obs;
import org.openmrs.TestOrder;
import org.openmrs.module.fhir2.FhirConstants;
import org.openmrs.module.fhir2.api.dao.FhirServiceRequestDao;
import org.openmrs.module.fhir2.api.search.param.SearchParameterMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Setter(AccessLevel.PACKAGE)
public class FhirServiceRequestDaoImpl extends BaseFhirDao<TestOrder> implements FhirServiceRequestDao<TestOrder> {

@Autowired
@Qualifier("fhirR4")
private FhirContext fhirContext;

@Override
protected void setupSearchParams(Criteria criteria, SearchParameterMap theParams) {
Expand All @@ -44,6 +58,9 @@ protected void setupSearchParams(Criteria criteria, SearchParameterMap theParams
entry.getValue().forEach(patientReference -> handlePatientReference(criteria,
(ReferenceAndListParam) patientReference.getParam(), "patient"));
break;
case FhirConstants.HAS_PROPERTY:
entry.getValue().forEach(hasParameter -> handleHasAndParam(criteria, (HasAndListParam) hasParameter.getParam()));
break;
case FhirConstants.CODED_SEARCH_HANDLER:
entry.getValue().forEach(code -> handleCodedConcept(criteria, (TokenAndListParam) code.getParam()));
break;
Expand Down Expand Up @@ -83,5 +100,47 @@ private Optional<Criterion> handleDateRange(DateRangeParam dateRangeParam) {
Optional.of(or(toCriteriaArray(Stream.of(handleDate("dateStopped", dateRangeParam.getUpperBound()),
handleDate("autoExpireDate", dateRangeParam.getUpperBound())))))))));
}

private void handleHasAndParam(Criteria criteria, HasAndListParam hasAndListParam) {
if (hasAndListParam == null || hasAndListParam.size() == 0) {
return;
}

DetachedCriteria obsSubquery = DetachedCriteria.forClass(Obs.class, "obs");
handleAndListParam(hasAndListParam, hasParam -> {
if (!FhirConstants.OBSERVATION.equals(hasParam.getTargetResourceType())) {
return Optional.empty();
}

if (hasParam.getParameterName() != null) {
switch (hasParam.getParameterName()) {
case Observation.SP_ENCOUNTER:
obsSubquery.createCriteria("encounter")
.add(Restrictions.eq("uuid", hasParam.getParameterValue().toString()));
break;
case Observation.SP_PATIENT:
obsSubquery.createCriteria("person")
.add(Restrictions.eq("uuid", hasParam.getParameterValue().toString()));
break;
case Observation.SP_CATEGORY:
obsSubquery.createCriteria("concept").createCriteria("conceptClass")
.add(Restrictions.eq("name", hasParam.getValueAsQueryToken(fhirContext).toString()));
break;
case Observation.SP_CODE:
obsSubquery.createCriteria("concept")
.add(Restrictions.eq("id", Integer.parseInt(hasParam.getParameterValue().toString())));
break;
}
} else {
return Optional.empty();
}
return Optional.empty();
}).ifPresent(obsSubquery::add);

obsSubquery.setProjection(property("obs.order"));
criteria.add(Subqueries.propertyIn("id", obsSubquery));
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public class FhirObservationServiceImpl extends BaseFhirService<Observation, org
@Override
@Transactional(readOnly = true)
public IBundleProvider searchForObservations(ReferenceAndListParam encounterReference,
ReferenceAndListParam patientReference, ReferenceAndListParam hasMemberReference, TokenAndListParam valueConcept,
ReferenceAndListParam patientReference, ReferenceAndListParam hasMemberReference,ReferenceAndListParam basedOnReference, TokenAndListParam valueConcept,
DateRangeParam valueDateParam, QuantityAndListParam valueQuantityParam, StringAndListParam valueStringParam,
DateRangeParam date, TokenAndListParam code, TokenAndListParam category, TokenAndListParam id,
DateRangeParam lastUpdated, SortSpec sort, HashSet<Include> includes, HashSet<Include> revIncludes) {
Expand All @@ -70,7 +70,8 @@ public IBundleProvider searchForObservations(ReferenceAndListParam encounterRefe
.addParameter(FhirConstants.CODED_SEARCH_HANDLER, code)
.addParameter(FhirConstants.CATEGORY_SEARCH_HANDLER, category)
.addParameter(FhirConstants.VALUE_CODED_SEARCH_HANDLER, valueConcept)
.addParameter(FhirConstants.HAS_MEMBER_SEARCH_HANDLER, hasMemberReference)
.addParameter(FhirConstants.HAS_MEMBER_SEARCH_HANDLER, hasMemberReference)
.addParameter(FhirConstants.BASED_ON_REFERENCE_SEARCH_HANDLER, basedOnReference)
.addParameter(FhirConstants.VALUE_STRING_SEARCH_HANDLER, "valueText", valueStringParam)
.addParameter(FhirConstants.QUANTITY_SEARCH_HANDLER, "valueNumeric", valueQuantityParam)
.addParameter(FhirConstants.DATE_RANGE_SEARCH_HANDLER, "obsDatetime", date)
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.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.HasAndListParam;
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import lombok.AccessLevel;
Expand Down Expand Up @@ -53,11 +54,12 @@ public class FhirServiceRequestServiceImpl extends BaseFhirService<ServiceReques
@Override
public IBundleProvider searchForServiceRequests(ReferenceAndListParam patientReference, TokenAndListParam code,
ReferenceAndListParam encounterReference, ReferenceAndListParam participantReference, DateRangeParam occurrence,
TokenAndListParam uuid, DateRangeParam lastUpdated, HashSet<Include> includes) {
TokenAndListParam uuid, DateRangeParam lastUpdated,HasAndListParam has, HashSet<Include> includes) {

SearchParameterMap theParams = new SearchParameterMap()
.addParameter(FhirConstants.PATIENT_REFERENCE_SEARCH_HANDLER, patientReference)
.addParameter(FhirConstants.CODED_SEARCH_HANDLER, code)
.addParameter(FhirConstants.CODED_SEARCH_HANDLER, code)
.addParameter(FhirConstants.HAS_PROPERTY, has)
.addParameter(FhirConstants.ENCOUNTER_REFERENCE_SEARCH_HANDLER, encounterReference)
.addParameter(FhirConstants.PARTICIPANT_REFERENCE_SEARCH_HANDLER, participantReference)
.addParameter(FhirConstants.DATE_RANGE_SEARCH_HANDLER, occurrence)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ private IBundleProvider handleObservationReverseInclude(ReferenceAndListParam pa
switch (targetType) {
case FhirConstants.OBSERVATION:
return observationService.searchForObservations(null, null, params, null, null, null, null, null, null, null,
null, null, null, null, null);
null, null, null, null, null, null);
case FhirConstants.DIAGNOSTIC_REPORT:
return diagnosticReportService.searchForDiagnosticReports(null, null, null, null, params, null, null, null,
null);
Expand All @@ -228,7 +228,7 @@ private IBundleProvider handlePractitionerReverseInclude(ReferenceAndListParam p
null);
case FhirConstants.PROCEDURE_REQUEST:
case FhirConstants.SERVICE_REQUEST:
return serviceRequestService.searchForServiceRequests(null, null, null, params, null, null, null, null);
return serviceRequestService.searchForServiceRequests(null, null, null, params, null, null, null, null, null);
}

return null;
Expand All @@ -238,7 +238,7 @@ private IBundleProvider handleEncounterReverseInclude(ReferenceAndListParam para
switch (targetType) {
case FhirConstants.OBSERVATION:
return observationService.searchForObservations(params, null, null, null, null, null, null, null, null, null,
null, null, null, null, null);
null, null, null, null, null, null);
case FhirConstants.DIAGNOSTIC_REPORT:
return diagnosticReportService.searchForDiagnosticReports(params, null, null, null, null, null, null, null,
null);
Expand All @@ -247,7 +247,7 @@ private IBundleProvider handleEncounterReverseInclude(ReferenceAndListParam para
null);
case FhirConstants.PROCEDURE_REQUEST:
case FhirConstants.SERVICE_REQUEST:
return serviceRequestService.searchForServiceRequests(null, null, params, null, null, null, null, null);
return serviceRequestService.searchForServiceRequests(null, null, params, null, null, null, null, null, null);
}

return null;
Expand All @@ -267,7 +267,7 @@ private IBundleProvider handlePatientReverseInclude(ReferenceAndListParam params
switch (targetType) {
case FhirConstants.OBSERVATION:
return observationService.searchForObservations(null, params, null, null, null, null, null, null, null, null,
null, null, null, null, null);
null, null, null, null, null, null);
case FhirConstants.DIAGNOSTIC_REPORT:
return diagnosticReportService.searchForDiagnosticReports(null, params, null, null, null, null, null, null,
null);
Expand All @@ -281,7 +281,7 @@ private IBundleProvider handlePatientReverseInclude(ReferenceAndListParam params
null);
case FhirConstants.SERVICE_REQUEST:
case FhirConstants.PROCEDURE_REQUEST:
return serviceRequestService.searchForServiceRequests(params, null, null, null, null, null, null, null);
return serviceRequestService.searchForServiceRequests(params, null, null, null, null, null, null, null, null);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.ServiceRequest;
import org.openmrs.module.fhir2.api.FhirObservationService;
import org.openmrs.module.fhir2.api.annotations.R3Provider;
import org.openmrs.module.fhir2.api.search.SearchQueryBundleProviderR3Wrapper;
Expand Down Expand Up @@ -116,6 +117,8 @@ public IBundleProvider searchObservations(
Patient.SP_FAMILY, Patient.SP_NAME }, targetTypes = Patient.class) ReferenceAndListParam patientParam,
@OptionalParam(name = Observation.SP_RELATED_TYPE, chainWhitelist = { "",
Observation.SP_CODE }, targetTypes = Observation.class) ReferenceAndListParam hasMemberReference,
@OptionalParam(name = Observation.SP_BASED_ON, chainWhitelist = { "",
ServiceRequest.SP_IDENTIFIER }, targetTypes = ServiceRequest.class) ReferenceAndListParam basedOnReference,
@OptionalParam(name = Observation.SP_VALUE_CONCEPT) TokenAndListParam valueConcept,
@OptionalParam(name = Observation.SP_VALUE_DATE) DateRangeParam valueDateParam,
@OptionalParam(name = Observation.SP_VALUE_QUANTITY) QuantityAndListParam valueQuantityParam,
Expand All @@ -142,8 +145,8 @@ public IBundleProvider searchObservations(
}

return new SearchQueryBundleProviderR3Wrapper(observationService.searchForObservations(encounterReference,
patientReference, hasMemberReference, valueConcept, valueDateParam, valueQuantityParam, valueStringParam, date,
code, category, id, lastUpdated, sort, includes, revIncludes));
patientReference, hasMemberReference, basedOnReference, valueConcept, valueDateParam, valueQuantityParam,
valueStringParam, date, code, category, id, lastUpdated, sort, includes, revIncludes));
}

@Operation(name = "lastn", idempotent = true, type = Observation.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import ca.uhn.fhir.rest.api.MethodOutcome;
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.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
Expand Down Expand Up @@ -114,7 +115,8 @@ public IBundleProvider searchForProcedureRequests(
Practitioner.SP_NAME }, targetTypes = Practitioner.class) ReferenceAndListParam participantReference,
@OptionalParam(name = ProcedureRequest.SP_OCCURRENCE) DateRangeParam occurrence,
@OptionalParam(name = ProcedureRequest.SP_RES_ID) TokenAndListParam uuid,
@OptionalParam(name = "_lastUpdated") DateRangeParam lastUpdated,
@OptionalParam(name = "_lastUpdated") DateRangeParam lastUpdated,
@OptionalParam(name = "_has") HasAndListParam has,
@IncludeParam(allow = { "ProcedureRequest:" + ProcedureRequest.SP_PATIENT,
"ProcedureRequest:" + ProcedureRequest.SP_REQUESTER,
"ProcedureRequest:" + ProcedureRequest.SP_ENCOUNTER }) HashSet<Include> includes) {
Expand All @@ -127,6 +129,6 @@ public IBundleProvider searchForProcedureRequests(
}

return new SearchQueryBundleProviderR3Wrapper(serviceRequestService.searchForServiceRequests(patientReference, code,
encounterReference, participantReference, occurrence, uuid, lastUpdated, includes));
encounterReference, participantReference, occurrence, uuid, lastUpdated,has, includes));
}
}
Loading

0 comments on commit 3676c0e

Please sign in to comment.