Changes per code review.

This commit is contained in:
ianmarshall 2020-05-29 23:47:44 -04:00
parent 5cc77b78d4
commit a4811f1508
25 changed files with 428 additions and 507 deletions

View File

@ -29,6 +29,15 @@ import javax.servlet.http.HttpServletResponse;
public interface IFhirResourceDaoObservation<T extends IBaseResource> extends IFhirResourceDao<T> { public interface IFhirResourceDaoObservation<T extends IBaseResource> extends IFhirResourceDao<T> {
/**
* Returns a BundleProvider which can be used to implement the $lastn operation.
* @param paramMap Parameters supported include Observation.subject, Observation.patient, Observation.code,
* Observation.category, and max (the maximum number of Observations to return per specified subjects/patients,
* codes, and/or categories.
* @param theRequestDetails
* @param theServletResponse
* @return
*/
IBundleProvider observationsLastN(SearchParameterMap paramMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse); IBundleProvider observationsLastN(SearchParameterMap paramMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse);
} }

View File

@ -22,25 +22,50 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.*; import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.*; import ca.uhn.fhir.rest.api.server.*;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.ReferenceParam;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.TreeMap; import java.util.TreeMap;
public abstract class BaseHapiFhirResourceDaoObservation<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> implements IFhirResourceDaoObservation<T> { public abstract class BaseHapiFhirResourceDaoObservation<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> implements IFhirResourceDaoObservation<T> {
@Autowired
ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc;
@Autowired @Autowired
private IRequestPartitionHelperSvc myRequestPartitionHelperService; private IRequestPartitionHelperSvc myRequestPartitionHelperService;
protected ResourceTable updateObservationEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity,
Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion,
theTransactionDetails, theForceUpdate, theCreateNewHistoryEntry);
if (!retVal.isUnchangedInCurrentOperation()) {
if (retVal.getDeleted() == null) {
// Update indexes here for LastN operation.
myObservationLastNIndexPersistSvc.indexObservation(theResource);
} else {
myObservationLastNIndexPersistSvc.deleteObservationIndex(theEntity);
}
}
return retVal;
}
protected void updateSearchParamsForLastn(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) { protected void updateSearchParamsForLastn(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) {
if (!isPagingProviderDatabaseBacked(theRequestDetails)) { if (!isPagingProviderDatabaseBacked(theRequestDetails)) {
theSearchParameterMap.setLoadSynchronous(true); theSearchParameterMap.setLoadSynchronous(true);

View File

@ -39,40 +39,46 @@ public class ObservationLastNIndexPersistSvc {
public void indexObservation(IBaseResource theResource) { public void indexObservation(IBaseResource theResource) {
String subjectId = null;
List<IBase> subjectReferenceElement = mySearchParameterExtractor.extractValues("Observation.subject", theResource); List<IBase> subjectReferenceElement = mySearchParameterExtractor.extractValues("Observation.subject", theResource);
if (subjectReferenceElement.size() == 1) { String subjectId = subjectReferenceElement.stream()
PathAndRef subjectPathAndRef = mySearchParameterExtractor.extractReferenceLinkFromResource(subjectReferenceElement.get(0), "Observation.subject"); .map(refElement -> mySearchParameterExtractor.extractReferenceLinkFromResource(refElement, "Observation.subject"))
if (subjectPathAndRef != null) { .filter(Objects::nonNull)
IBaseReference subjectReference = subjectPathAndRef.getRef(); .map(PathAndRef::getRef)
if (subjectReference != null) { .filter(Objects::nonNull)
subjectId = subjectReference.getReferenceElement().getValue(); .map(subjectRef -> subjectRef.getReferenceElement().getValue())
} .filter(Objects::nonNull)
} .findFirst().orElse(null);
}
Date effectiveDtm = null; Date effectiveDtm = null;
List<IBase> effectiveDateElement = mySearchParameterExtractor.extractValues("Observation.effective", theResource); List<IBase> effectiveDateElement = mySearchParameterExtractor.extractValues("Observation.effective", theResource);
if (effectiveDateElement.size() == 1) { if (effectiveDateElement.size() > 0) {
effectiveDtm = mySearchParameterExtractor.extractDateFromResource(effectiveDateElement.get(0), "Observation.effective"); effectiveDtm = mySearchParameterExtractor.extractDateFromResource(effectiveDateElement.get(0), "Observation.effective");
} }
// Build CodeableConcept entity for Observation.Code.
List<IBase> observationCodeCodeableConcepts = mySearchParameterExtractor.extractValues("Observation.code", theResource); List<IBase> observationCodeCodeableConcepts = mySearchParameterExtractor.extractValues("Observation.code", theResource);
// Only index for lastn if Observation has a code
// Only index for lastn if Observation has a subject, effective date/time and code if (observationCodeCodeableConcepts.size() == 0) {
if (subjectId == null || effectiveDtm == null || observationCodeCodeableConcepts.size() == 0) {
return; return;
} }
List<IBase> observationCategoryCodeableConcepts = mySearchParameterExtractor.extractValues("Observation.category", theResource);
String resourcePID = theResource.getIdElement().getIdPart(); String resourcePID = theResource.getIdElement().getIdPart();
createOrUpdateIndexedObservation(resourcePID, effectiveDtm, subjectId, observationCodeCodeableConcepts, observationCategoryCodeableConcepts);
}
private void createOrUpdateIndexedObservation(String resourcePID, Date theEffectiveDtm, String theSubjectId,
List<IBase> theObservationCodeCodeableConcepts,
List<IBase> theObservationCategoryCodeableConcepts) {
// Determine if an index already exists for Observation: // Determine if an index already exists for Observation:
boolean observationIndexUpdate = false; boolean observationIndexUpdate = false;
ObservationIndexedSearchParamLastNEntity indexedObservation = null; ObservationIndexedSearchParamLastNEntity indexedObservation = null;
if (resourcePID != null) { if (resourcePID != null) {
indexedObservation = myResourceIndexedObservationLastNDao.findForIdentifier(resourcePID); indexedObservation = myResourceIndexedObservationLastNDao.findByIdentifier(resourcePID);
} }
if (indexedObservation == null) { if (indexedObservation == null) {
indexedObservation = new ObservationIndexedSearchParamLastNEntity(); indexedObservation = new ObservationIndexedSearchParamLastNEntity();
@ -80,27 +86,46 @@ public class ObservationLastNIndexPersistSvc {
observationIndexUpdate = true; observationIndexUpdate = true;
} }
indexedObservation.setEffectiveDtm(effectiveDtm); indexedObservation.setEffectiveDtm(theEffectiveDtm);
indexedObservation.setIdentifier(resourcePID); indexedObservation.setIdentifier(resourcePID);
indexedObservation.setSubject(subjectId); indexedObservation.setSubject(theSubjectId);
addCodeToObservationIndex(theObservationCodeCodeableConcepts, indexedObservation);
addCategoriesToObservationIndex(theObservationCategoryCodeableConcepts, indexedObservation);
if (observationIndexUpdate) {
myEntityManager.merge(indexedObservation);
} else {
myEntityManager.persist(indexedObservation);
}
}
private void addCodeToObservationIndex(List<IBase> theObservationCodeCodeableConcepts,
ObservationIndexedSearchParamLastNEntity theIndexedObservation) {
// Determine if a Normalized ID was created previously for Observation Code // Determine if a Normalized ID was created previously for Observation Code
boolean observationCodeUpdate = false; Optional<String> existingObservationCodeNormalizedId = getCodeCodeableConceptIdIfExists(theObservationCodeCodeableConcepts.get(0));
String observationCodeNormalizedId = getCodeCodeableConceptIdIfExists(observationCodeCodeableConcepts.get(0));
if (observationCodeNormalizedId != null) {
observationCodeUpdate = true;
}
// Generate a new a normalized ID if necessary
if (observationCodeNormalizedId == null) {
observationCodeNormalizedId = UUID.randomUUID().toString();
}
// Create/update normalized Observation Code index record // Create/update normalized Observation Code index record
ObservationIndexedCodeCodeableConceptEntity codeableConceptField = getCodeCodeableConcept(observationCodeCodeableConcepts.get(0), observationCodeNormalizedId); ObservationIndexedCodeCodeableConceptEntity codeableConceptField =
getCodeCodeableConcept(theObservationCodeCodeableConcepts.get(0),
existingObservationCodeNormalizedId.orElse(UUID.randomUUID().toString()));
if (existingObservationCodeNormalizedId.isPresent()) {
myEntityManager.merge(codeableConceptField);
} else {
myEntityManager.persist(codeableConceptField);
}
theIndexedObservation.setObservationCode(codeableConceptField);
theIndexedObservation.setCodeNormalizedId(codeableConceptField.getCodeableConceptId());
}
private void addCategoriesToObservationIndex(List<IBase> observationCategoryCodeableConcepts,
ObservationIndexedSearchParamLastNEntity indexedObservation) {
// Build CodeableConcept entities for Observation.Category // Build CodeableConcept entities for Observation.Category
List<IBase> observationCategoryCodeableConcepts = mySearchParameterExtractor.extractValues("Observation.category", theResource);
Set<ObservationIndexedCategoryCodeableConceptEntity> categoryCodeableConceptEntities = new HashSet<>(); Set<ObservationIndexedCategoryCodeableConceptEntity> categoryCodeableConceptEntities = new HashSet<>();
for (IBase categoryCodeableConcept : observationCategoryCodeableConcepts) { for (IBase categoryCodeableConcept : observationCategoryCodeableConcepts) {
// Build CodeableConcept entities for each category CodeableConcept // Build CodeableConcept entities for each category CodeableConcept
@ -108,20 +133,6 @@ public class ObservationLastNIndexPersistSvc {
} }
indexedObservation.setCategoryCodeableConcepts(categoryCodeableConceptEntities); indexedObservation.setCategoryCodeableConcepts(categoryCodeableConceptEntities);
if (observationCodeUpdate) {
myEntityManager.merge(codeableConceptField);
} else {
myEntityManager.persist(codeableConceptField);
}
indexedObservation.setObservationCode(codeableConceptField);
indexedObservation.setCodeNormalizedId(observationCodeNormalizedId);
if (observationIndexUpdate) {
myEntityManager.merge(indexedObservation);
} else {
myEntityManager.persist(indexedObservation);
}
} }
private ObservationIndexedCategoryCodeableConceptEntity getCategoryCodeableConceptEntities(IBase theValue) { private ObservationIndexedCategoryCodeableConceptEntity getCategoryCodeableConceptEntities(IBase theValue) {
@ -151,29 +162,32 @@ public class ObservationLastNIndexPersistSvc {
return codeCodeableConcept; return codeCodeableConcept;
} }
private String getCodeCodeableConceptIdIfExists(IBase theValue) { private Optional<String> getCodeCodeableConceptIdIfExists(IBase theValue) {
List<IBase> codings = mySearchParameterExtractor.getCodingsFromCodeableConcept(theValue); List<IBase> codings = mySearchParameterExtractor.getCodingsFromCodeableConcept(theValue);
String codeCodeableConceptId = null; String codeCodeableConceptId = null;
Optional<String> codeCodeableConceptIdOptional = Optional.empty();
for (IBase nextCoding : codings) { for (IBase nextCoding : codings) {
ResourceIndexedSearchParamToken param = mySearchParameterExtractor.createSearchParamForCoding("Observation", ResourceIndexedSearchParamToken param = mySearchParameterExtractor.createSearchParamForCoding("Observation",
new RuntimeSearchParam(null, null, "code", null, null, null, null, null, null, null), new RuntimeSearchParam(null, null, "code", null, null, null,
null, null, null, null),
nextCoding); nextCoding);
if (param != null) { if (param != null) {
String system = param.getSystem(); String system = param.getSystem();
String code = param.getValue(); String code = param.getValue();
String text = mySearchParameterExtractor.getDisplayTextForCoding(nextCoding); String text = mySearchParameterExtractor.getDisplayTextForCoding(nextCoding);
if (code != null && system != null) { if (code != null && system != null) {
codeCodeableConceptId = myObservationIndexedCodeCodingSearchParamDao.findForCodeAndSystem(code, system); codeCodeableConceptIdOptional = Optional.ofNullable(myObservationIndexedCodeCodingSearchParamDao.findByCodeAndSystem(code, system));
} else { } else {
codeCodeableConceptId = myObservationIndexedCodeCodingSearchParamDao.findForDisplay(text); codeCodeableConceptIdOptional = Optional.ofNullable(myObservationIndexedCodeCodingSearchParamDao.findByDisplay(text));
} }
if (codeCodeableConceptId != null) { if (codeCodeableConceptIdOptional.isPresent()) {
break; break;
} }
} }
} }
return codeCodeableConceptId; return codeCodeableConceptIdOptional;
} }
private ObservationIndexedCategoryCodingEntity getCategoryCoding(IBase theValue) { private ObservationIndexedCategoryCodingEntity getCategoryCoding(IBase theValue) {
@ -205,7 +219,7 @@ public class ObservationLastNIndexPersistSvc {
} }
public void deleteObservationIndex(IBasePersistedResource theEntity) { public void deleteObservationIndex(IBasePersistedResource theEntity) {
ObservationIndexedSearchParamLastNEntity deletedObservationLastNEntity = myResourceIndexedObservationLastNDao.findForIdentifier(theEntity.getIdDt().getIdPart()); ObservationIndexedSearchParamLastNEntity deletedObservationLastNEntity = myResourceIndexedObservationLastNDao.findByIdentifier(theEntity.getIdDt().getIdPart());
if (deletedObservationLastNEntity != null) { if (deletedObservationLastNEntity != null) {
myEntityManager.remove(deletedObservationLastNEntity); myEntityManager.remove(deletedObservationLastNEntity);
} }

View File

@ -131,7 +131,7 @@ public class SearchBuilder implements ISearchBuilder {
// NB: keep public // NB: keep public
public static final int MAXIMUM_PAGE_SIZE = 800; public static final int MAXIMUM_PAGE_SIZE = 800;
public static final int MAXIMUM_PAGE_SIZE_FOR_TESTING = 50; public static final int MAXIMUM_PAGE_SIZE_FOR_TESTING = 50;
public static boolean myIsTest = false; public static boolean myUseMaxPageSize50ForTest = false;
private static final List<ResourcePersistentId> EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>()); private static final List<ResourcePersistentId> EMPTY_LONG_LIST = Collections.unmodifiableList(new ArrayList<>());
private static final Logger ourLog = LoggerFactory.getLogger(SearchBuilder.class); private static final Logger ourLog = LoggerFactory.getLogger(SearchBuilder.class);
@ -184,15 +184,15 @@ public class SearchBuilder implements ISearchBuilder {
} }
public static int getMaximumPageSize() { public static int getMaximumPageSize() {
if (myIsTest) { if (myUseMaxPageSize50ForTest) {
return MAXIMUM_PAGE_SIZE_FOR_TESTING; return MAXIMUM_PAGE_SIZE_FOR_TESTING;
} else { } else {
return MAXIMUM_PAGE_SIZE; return MAXIMUM_PAGE_SIZE;
} }
} }
public static void setIsTest(boolean theIsTest) { public static void setMaxPageSize50ForTest(boolean theIsTest) {
myIsTest = theIsTest; myUseMaxPageSize50ForTest = theIsTest;
} }
@Override @Override

View File

@ -14,13 +14,13 @@ public interface IObservationIndexedCodeCodingSearchParamDao extends JpaReposito
"WHERE t.myCode = :code " + "WHERE t.myCode = :code " +
"AND t.mySystem = :system " + "AND t.mySystem = :system " +
"") "")
String findForCodeAndSystem(@Param("code") String theCode, @Param("system") String theSystem); String findByCodeAndSystem(@Param("code") String theCode, @Param("system") String theSystem);
@Query("" + @Query("" +
"SELECT t.myCodeableConceptId FROM ObservationIndexedCodeCodingEntity t " + "SELECT t.myCodeableConceptId FROM ObservationIndexedCodeCodingEntity t " +
"WHERE t.myDisplay = :display" + "WHERE t.myDisplay = :display" +
"") "")
String findForDisplay(@Param("display") String theDisplay); String findByDisplay(@Param("display") String theDisplay);
} }

View File

@ -12,6 +12,6 @@ public interface IObservationIndexedSearchParamLastNDao extends JpaRepository<Ob
"SELECT t FROM ObservationIndexedSearchParamLastNEntity t " + "SELECT t FROM ObservationIndexedSearchParamLastNEntity t " +
"WHERE t.myIdentifier = :identifier" + "WHERE t.myIdentifier = :identifier" +
"") "")
ObservationIndexedSearchParamLastNEntity findForIdentifier(@Param("identifier") String theIdentifier); ObservationIndexedSearchParamLastNEntity findByIdentifier(@Param("identifier") String theIdentifier);
} }

View File

@ -71,21 +71,11 @@ public class FhirResourceDaoObservationDstu3 extends BaseHapiFhirResourceDaoObse
} }
@Override @Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate, theCreateNewHistoryEntry); return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
if (!retVal.isUnchangedInCurrentOperation()) { theCreateNewHistoryEntry);
if (retVal.getDeleted() == null) {
// Update indexes here for LastN operation.
Observation observation = (Observation) theResource;
myObservationLastNIndexPersistSvc.indexObservation(observation);
} else {
myObservationLastNIndexPersistSvc.deleteObservationIndex(theEntity);
}
}
return retVal;
} }
} }

View File

@ -78,21 +78,11 @@ public class FhirResourceDaoObservationR4 extends BaseHapiFhirResourceDaoObserva
} }
@Override @Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate, theCreateNewHistoryEntry); return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
if (!retVal.isUnchangedInCurrentOperation()) { theCreateNewHistoryEntry);
if (retVal.getDeleted() == null) {
// Update indexes here for LastN operation.
Observation observation = (Observation) theResource;
myObservationLastNIndexPersistSvc.indexObservation(observation);
} else {
myObservationLastNIndexPersistSvc.deleteObservationIndex(retVal);
}
}
return retVal;
} }
} }

View File

@ -71,21 +71,11 @@ public class FhirResourceDaoObservationR5 extends BaseHapiFhirResourceDaoObserva
} }
@Override @Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate, theCreateNewHistoryEntry); return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
if (!retVal.isUnchangedInCurrentOperation()) { theCreateNewHistoryEntry);
if (retVal.getDeleted() == null) {
// Update indexes here for LastN operation.
Observation observation = (Observation) theResource;
myObservationLastNIndexPersistSvc.indexObservation(observation);
} else {
myObservationLastNIndexPersistSvc.deleteObservationIndex(theEntity);
}
}
return retVal;
} }
} }

View File

@ -1,22 +1,22 @@
package ca.uhn.fhir.jpa.search.lastn; package ca.uhn.fhir.jpa.search.lastn;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import ca.uhn.fhir.jpa.search.lastn.json.CodeJson;
import ca.uhn.fhir.jpa.search.lastn.json.ObservationJson;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.util.LastNParameterHelper; import ca.uhn.fhir.jpa.searchparam.util.LastNParameterHelper;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.jpa.search.lastn.json.CodeJson;
import ca.uhn.fhir.jpa.search.lastn.json.ObservationJson;
import ca.uhn.fhir.jpa.search.lastn.util.CodeSystemHash;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import org.shadehapi.elasticsearch.action.DocWriteResponse;
import org.shadehapi.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.shadehapi.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.shadehapi.elasticsearch.action.admin.indices.create.CreateIndexResponse; import org.shadehapi.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.shadehapi.elasticsearch.action.admin.indices.get.GetIndexRequest; import org.shadehapi.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.shadehapi.elasticsearch.action.DocWriteResponse;
import org.shadehapi.elasticsearch.action.index.IndexRequest; import org.shadehapi.elasticsearch.action.index.IndexRequest;
import org.shadehapi.elasticsearch.action.index.IndexResponse; import org.shadehapi.elasticsearch.action.index.IndexResponse;
import org.shadehapi.elasticsearch.action.search.SearchRequest; import org.shadehapi.elasticsearch.action.search.SearchRequest;
@ -33,7 +33,10 @@ import org.shadehapi.elasticsearch.search.aggregations.AggregationBuilder;
import org.shadehapi.elasticsearch.search.aggregations.AggregationBuilders; import org.shadehapi.elasticsearch.search.aggregations.AggregationBuilders;
import org.shadehapi.elasticsearch.search.aggregations.Aggregations; import org.shadehapi.elasticsearch.search.aggregations.Aggregations;
import org.shadehapi.elasticsearch.search.aggregations.BucketOrder; import org.shadehapi.elasticsearch.search.aggregations.BucketOrder;
import org.shadehapi.elasticsearch.search.aggregations.bucket.composite.*; import org.shadehapi.elasticsearch.search.aggregations.bucket.composite.CompositeAggregationBuilder;
import org.shadehapi.elasticsearch.search.aggregations.bucket.composite.CompositeValuesSourceBuilder;
import org.shadehapi.elasticsearch.search.aggregations.bucket.composite.ParsedComposite;
import org.shadehapi.elasticsearch.search.aggregations.bucket.composite.TermsValuesSourceBuilder;
import org.shadehapi.elasticsearch.search.aggregations.bucket.terms.ParsedTerms; import org.shadehapi.elasticsearch.search.aggregations.bucket.terms.ParsedTerms;
import org.shadehapi.elasticsearch.search.aggregations.bucket.terms.Terms; import org.shadehapi.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.shadehapi.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; import org.shadehapi.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
@ -42,7 +45,9 @@ import org.shadehapi.elasticsearch.search.aggregations.support.ValueType;
import org.shadehapi.elasticsearch.search.builder.SearchSourceBuilder; import org.shadehapi.elasticsearch.search.builder.SearchSourceBuilder;
import org.shadehapi.elasticsearch.search.sort.SortOrder; import org.shadehapi.elasticsearch.search.sort.SortOrder;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
@ -52,9 +57,11 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class ElasticsearchSvcImpl implements IElasticsearchSvc { public class ElasticsearchSvcImpl implements IElasticsearchSvc {
public static final String OBSERVATION_INDEX = "observation_index"; public static final String OBSERVATION_INDEX = "observation_index";
public static final String CODE_INDEX = "code_index"; public static final String OBSERVATION_CODE_INDEX = "code_index";
public static final String OBSERVATION_DOCUMENT_TYPE = "ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity"; public static final String OBSERVATION_DOCUMENT_TYPE = "ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity";
public static final String CODE_DOCUMENT_TYPE = "ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity"; public static final String CODE_DOCUMENT_TYPE = "ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity";
public static final String OBSERVATION_INDEX_SCHEMA_FILE = "ObservationIndexSchema.json";
public static final String OBSERVATION_CODE_INDEX_SCHEMA_FILE = "ObservationCodeIndexSchema.json";
private final RestHighLevelClient myRestHighLevelClient; private final RestHighLevelClient myRestHighLevelClient;
@ -63,6 +70,7 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
private final String GROUP_BY_SUBJECT = "group_by_subject"; private final String GROUP_BY_SUBJECT = "group_by_subject";
private final String GROUP_BY_SYSTEM = "group_by_system"; private final String GROUP_BY_SYSTEM = "group_by_system";
private final String GROUP_BY_CODE = "group_by_code"; private final String GROUP_BY_CODE = "group_by_code";
private final String OBSERVATION_IDENTIFIER_FIELD_NAME = "identifier";
public ElasticsearchSvcImpl(String theHostname, int thePort, String theUsername, String thePassword) { public ElasticsearchSvcImpl(String theHostname, int thePort, String theUsername, String thePassword) {
@ -70,106 +78,41 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
try { try {
createObservationIndexIfMissing(); createObservationIndexIfMissing();
createCodeIndexIfMissing(); createObservationCodeIndexIfMissing();
} catch (IOException theE) { } catch (IOException theE) {
throw new RuntimeException("Failed to create document index", theE); throw new RuntimeException("Failed to create document index", theE);
} }
} }
private String getIndexSchema(String theSchemaFileName) throws IOException {
InputStreamReader input = new InputStreamReader(ElasticsearchSvcImpl.class.getResourceAsStream(theSchemaFileName));
BufferedReader reader = new BufferedReader(input);
StringBuilder sb = new StringBuilder();
String str;
while((str = reader.readLine())!= null){
sb.append(str);
}
return sb.toString();
}
private void createObservationIndexIfMissing() throws IOException { private void createObservationIndexIfMissing() throws IOException {
if (indexExists(OBSERVATION_INDEX)) { if (indexExists(OBSERVATION_INDEX)) {
return; return;
} }
String observationMapping = "{\n" + String observationMapping = getIndexSchema(OBSERVATION_INDEX_SCHEMA_FILE);
" \"mappings\" : {\n" +
" \"ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity\" : {\n" +
" \"properties\" : {\n" +
" \"codeconceptid\" : {\n" +
" \"type\" : \"keyword\",\n" +
" \"norms\" : true\n" +
" },\n" +
" \"codeconcepttext\" : {\n" +
" \"type\" : \"text\"\n" +
" },\n" +
" \"codeconceptcodingcode\" : {\n" +
" \"type\" : \"keyword\"\n" +
" },\n" +
" \"codeconceptcodingsystem\" : {\n" +
" \"type\" : \"keyword\"\n" +
" },\n" +
" \"codeconceptcodingcode_system_hash\" : {\n" +
" \"type\" : \"keyword\"\n" +
" },\n" +
" \"codeconceptcodingdisplay\" : {\n" +
" \"type\" : \"text\"\n" +
" },\n" +
" \"categoryconcepttext\" : {\n" +
" \"type\" : \"text\"\n" +
" },\n" +
" \"categoryconceptcodingcode\" : {\n" +
" \"type\" : \"keyword\"\n" +
" },\n" +
" \"categoryconceptcodingsystem\" : {\n" +
" \"type\" : \"keyword\"\n" +
" },\n" +
" \"categoryconceptcodingcode_system_hash\" : {\n" +
" \"type\" : \"keyword\"\n" +
" },\n" +
" \"categoryconceptcodingdisplay\" : {\n" +
" \"type\" : \"text\"\n" +
" },\n" +
" \"effectivedtm\" : {\n" +
" \"type\" : \"date\"\n" +
" },\n" +
" \"identifier\" : {\n" +
" \"type\" : \"keyword\",\n" +
" \"store\" : true\n" +
" },\n" +
" \"subject\" : {\n" +
" \"type\" : \"keyword\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}\n";
if (!createIndex(OBSERVATION_INDEX, observationMapping)) { if (!createIndex(OBSERVATION_INDEX, observationMapping)) {
throw new RuntimeException("Failed to create observation index"); throw new RuntimeException("Failed to create observation index");
} }
} }
private void createCodeIndexIfMissing() throws IOException { private void createObservationCodeIndexIfMissing() throws IOException {
if (indexExists(CODE_INDEX)) { if (indexExists(OBSERVATION_CODE_INDEX)) {
return; return;
} }
String codeMapping = "{\n" + String observationCodeMapping = getIndexSchema(OBSERVATION_CODE_INDEX_SCHEMA_FILE);
" \"mappings\" : {\n" + if (!createIndex(OBSERVATION_CODE_INDEX, observationCodeMapping)) {
" \"ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity\" : {\n" + throw new RuntimeException("Failed to create observation code index");
" \"properties\" : {\n" +
" \"codeable_concept_id\" : {\n" +
" \"type\" : \"keyword\",\n" +
" \"store\" : true\n" +
" },\n" +
" \"codingcode\" : {\n" +
" \"type\" : \"keyword\"\n" +
" },\n" +
" \"codingcode_system_hash\" : {\n" +
" \"type\" : \"keyword\"\n" +
" },\n" +
" \"codingdisplay\" : {\n" +
" \"type\" : \"text\"\n" +
" },\n" +
" \"codingsystem\" : {\n" +
" \"type\" : \"keyword\"\n" +
" },\n" +
" \"text\" : {\n" +
" \"type\" : \"text\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}\n";
if (!createIndex(CODE_INDEX, codeMapping)) {
throw new RuntimeException("Failed to create code index");
} }
} }
@ -190,7 +133,6 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
@Override @Override
public List<String> executeLastN(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext, Integer theMaxResultsToFetch) { public List<String> executeLastN(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext, Integer theMaxResultsToFetch) {
String OBSERVATION_IDENTIFIER_FIELD_NAME = "identifier";
String[] topHitsInclude = {OBSERVATION_IDENTIFIER_FIELD_NAME}; String[] topHitsInclude = {OBSERVATION_IDENTIFIER_FIELD_NAME};
return buildAndExecuteSearch(theSearchParameterMap, theFhirContext, topHitsInclude, return buildAndExecuteSearch(theSearchParameterMap, theFhirContext, topHitsInclude,
ObservationJson::getIdentifier, theMaxResultsToFetch); ObservationJson::getIdentifier, theMaxResultsToFetch);
@ -557,7 +499,7 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
@VisibleForTesting @VisibleForTesting
List<CodeJson> queryAllIndexedObservationCodes() throws IOException { List<CodeJson> queryAllIndexedObservationCodes() throws IOException {
SearchRequest codeSearchRequest = new SearchRequest(CODE_INDEX); SearchRequest codeSearchRequest = new SearchRequest(OBSERVATION_CODE_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// Query // Query
searchSourceBuilder.query(QueryBuilders.matchAllQuery()); searchSourceBuilder.query(QueryBuilders.matchAllQuery());

View File

@ -6,5 +6,13 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import java.util.List; import java.util.List;
public interface IElasticsearchSvc { public interface IElasticsearchSvc {
/**
* Returns identifiers for the last most recent N observations that meet the specified criteria.
* @param theSearchParameterMap SearchParameterMap containing search parameters used for filtering the last N observations. Supported parameters include Subject, Patient, Code, Category and Max (the parameter used to determine N).
* @param theFhirContext Current FhirContext.
* @param theMaxResultsToFetch The maximum number of results to return for the purpose of paging.
* @return
*/
List<String> executeLastN(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext, Integer theMaxResultsToFetch); List<String> executeLastN(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext, Integer theMaxResultsToFetch);
} }

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.search.lastn.json;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.search.lastn.util.CodeSystemHash; import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.search.lastn.json;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.search.lastn.util.CodeSystemHash; import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
@ -25,148 +25,117 @@ import java.util.List;
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) @JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class ObservationJson { public class ObservationJson {
@JsonProperty(value = "identifier", required = true) @JsonProperty(value = "identifier", required = true)
private String myIdentifier; private String myIdentifier;
@JsonProperty(value = "subject", required = true) @JsonProperty(value = "subject", required = true)
private String mySubject; private String mySubject;
@JsonProperty(value = "categoryconcepttext", required = false) @JsonProperty(value = "categoryconcepttext", required = false)
private List<String> myCategory_concept_text = new ArrayList<>(); private List<String> myCategory_concept_text = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingcode", required = false) @JsonProperty(value = "categoryconceptcodingcode", required = false)
private List<List<String>> myCategory_coding_code = new ArrayList<>(); private List<List<String>> myCategory_coding_code = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingcode_system_hash", required = false) @JsonProperty(value = "categoryconceptcodingcode_system_hash", required = false)
private List<List<String>> myCategory_coding_code_system_hash = new ArrayList<>(); private List<List<String>> myCategory_coding_code_system_hash = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingdisplay", required = false) @JsonProperty(value = "categoryconceptcodingdisplay", required = false)
private List<List<String>> myCategory_coding_display = new ArrayList<>(); private List<List<String>> myCategory_coding_display = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingsystem", required = false) @JsonProperty(value = "categoryconceptcodingsystem", required = false)
private List<List<String>> myCategory_coding_system = new ArrayList<>(); private List<List<String>> myCategory_coding_system = new ArrayList<>();
@JsonProperty(value = "codeconceptid", required = false) @JsonProperty(value = "codeconceptid", required = false)
private String myCode_concept_id; private String myCode_concept_id;
@JsonProperty(value = "codeconcepttext", required = false) @JsonProperty(value = "codeconcepttext", required = false)
private String myCode_concept_text; private String myCode_concept_text;
@JsonProperty(value = "codeconceptcodingcode", required = false) @JsonProperty(value = "codeconceptcodingcode", required = false)
// TODO: Temporary change until sort out how to deal with multiple observation code codings private String myCode_coding_code;
// private List<String> myCode_coding_code = new ArrayList<>();
private String myCode_coding_code;
@JsonProperty(value = "codeconceptcodingcode_system_hash", required = false) @JsonProperty(value = "codeconceptcodingcode_system_hash", required = false)
// TODO: Temporary change until sort out how to deal with multiple observation code codings private String myCode_coding_code_system_hash;
// private List<String> myCode_coding_code_system_hash = new ArrayList<>();
private String myCode_coding_code_system_hash;
@JsonProperty(value = "codeconceptcodingdisplay", required = false) @JsonProperty(value = "codeconceptcodingdisplay", required = false)
// TODO: Temporary change until sort out how to deal with multiple observation code codings private String myCode_coding_display;
// private List<String> myCode_coding_display = new ArrayList<>();
private String myCode_coding_display;
@JsonProperty(value = "codeconceptcodingsystem", required = false) @JsonProperty(value = "codeconceptcodingsystem", required = false)
// TODO: Temporary change until sort out how to deal with multiple observation code codings private String myCode_coding_system;
// private List<String> myCode_coding_system = new ArrayList<>();
private String myCode_coding_system;
@JsonProperty(value = "effectivedtm", required = true) @JsonProperty(value = "effectivedtm", required = true)
private Date myEffectiveDtm; private Date myEffectiveDtm;
public ObservationJson() {} public ObservationJson() {
}
public void setIdentifier(String theIdentifier) { public void setIdentifier(String theIdentifier) {
myIdentifier = theIdentifier; myIdentifier = theIdentifier;
} }
public void setSubject(String theSubject) { public void setSubject(String theSubject) {
mySubject = theSubject; mySubject = theSubject;
} }
public void setCategories(List<CodeableConcept> theCategories) { public void setCategories(List<CodeableConcept> theCategories) {
for (CodeableConcept theConcept : theCategories) { for (CodeableConcept theConcept : theCategories) {
myCategory_concept_text.add(theConcept.getText()); myCategory_concept_text.add(theConcept.getText());
List<String> coding_code_system_hashes = new ArrayList<>(); List<String> coding_code_system_hashes = new ArrayList<>();
List<String> coding_codes = new ArrayList<>(); List<String> coding_codes = new ArrayList<>();
List<String> coding_displays = new ArrayList<>(); List<String> coding_displays = new ArrayList<>();
List<String> coding_systems = new ArrayList<>(); List<String> coding_systems = new ArrayList<>();
for (Coding theCategoryCoding : theConcept.getCoding()) { for (Coding theCategoryCoding : theConcept.getCoding()) {
coding_code_system_hashes.add(String.valueOf(CodeSystemHash.hashCodeSystem(theCategoryCoding.getSystem(), theCategoryCoding.getCode()))); coding_code_system_hashes.add(String.valueOf(CodeSystemHash.hashCodeSystem(theCategoryCoding.getSystem(), theCategoryCoding.getCode())));
coding_codes.add(theCategoryCoding.getCode()); coding_codes.add(theCategoryCoding.getCode());
coding_displays.add(theCategoryCoding.getDisplay()); coding_displays.add(theCategoryCoding.getDisplay());
coding_systems.add(theCategoryCoding.getSystem()); coding_systems.add(theCategoryCoding.getSystem());
} }
myCategory_coding_code_system_hash.add(coding_code_system_hashes); myCategory_coding_code_system_hash.add(coding_code_system_hashes);
myCategory_coding_code.add(coding_codes); myCategory_coding_code.add(coding_codes);
myCategory_coding_display.add(coding_displays); myCategory_coding_display.add(coding_displays);
myCategory_coding_system.add(coding_systems); myCategory_coding_system.add(coding_systems);
} }
} }
public List<String> getCategory_concept_text() { public List<String> getCategory_concept_text() {
return myCategory_concept_text; return myCategory_concept_text;
} }
public List<List<String>> getCategory_coding_code_system_hash() { public List<List<String>> getCategory_coding_code_system_hash() {
return myCategory_coding_code_system_hash; return myCategory_coding_code_system_hash;
} }
public List<List<String>> getCategory_coding_code() { public List<List<String>> getCategory_coding_code() {
return myCategory_coding_code; return myCategory_coding_code;
} }
public List<List<String>> getCategory_coding_display() { public List<List<String>> getCategory_coding_display() {
return myCategory_coding_display; return myCategory_coding_display;
} }
public List<List<String>> getCategory_coding_system() { public List<List<String>> getCategory_coding_system() {
return myCategory_coding_system; return myCategory_coding_system;
} }
public void setCode(CodeableConcept theCode) { public void setCode(CodeableConcept theCode) {
myCode_concept_text = theCode.getText(); myCode_concept_text = theCode.getText();
for(Coding theCodeCoding : theCode.getCoding()) { for (Coding theCodeCoding : theCode.getCoding()) {
// TODO: Temporary change until address how to manage multiple Observation Code codings. myCode_coding_code_system_hash = String.valueOf(CodeSystemHash.hashCodeSystem(theCodeCoding.getSystem(), theCodeCoding.getCode()));
/* myCode_coding_code_system_hash.add(String.valueOf(CodeSystemHash.hashCodeSystem(theCodeCoding.getSystem(), theCodeCoding.getCode()))); myCode_coding_code = theCodeCoding.getCode();
myCode_coding_code.add(theCodeCoding.getCode()); myCode_coding_display = theCodeCoding.getDisplay();
myCode_coding_display.add(theCodeCoding.getDisplay()); myCode_coding_system = theCodeCoding.getSystem();
myCode_coding_system.add(theCodeCoding.getSystem()); }
*/
myCode_coding_code_system_hash = String.valueOf(CodeSystemHash.hashCodeSystem(theCodeCoding.getSystem(), theCodeCoding.getCode()));
myCode_coding_code = theCodeCoding.getCode();
myCode_coding_display = theCodeCoding.getDisplay();
myCode_coding_system = theCodeCoding.getSystem();
}
} }
public String getCode_concept_text() { public String getCode_concept_text() {
return myCode_concept_text; return myCode_concept_text;
} }
// TODO: Temporary changes until resolve problem of how to manage Observation Code with multiple codings public String getCode_coding_code_system_hash() {
/* return myCode_coding_code_system_hash;
public List<String> getCode_coding_code_system_hash() { }
return myCode_coding_code_system_hash;
}
public List<String> getCode_coding_code() {
return myCode_coding_code;
}
public List<String> getCode_coding_display() {
return myCode_coding_display;
}
public List<String> getCode_coding_system() {
return myCode_coding_system;
}
*/
public String getCode_coding_code_system_hash() {
return myCode_coding_code_system_hash;
}
public String getCode_coding_code() { public String getCode_coding_code() {
return myCode_coding_code; return myCode_coding_code;
@ -180,28 +149,28 @@ public class ObservationJson {
return myCode_coding_system; return myCode_coding_system;
} }
public void setCode_concept_id(String theCodeId) { public void setCode_concept_id(String theCodeId) {
myCode_concept_id = theCodeId; myCode_concept_id = theCodeId;
} }
public String getCode_concept_id() { public String getCode_concept_id() {
return myCode_concept_id; return myCode_concept_id;
} }
public void setEffectiveDtm(Date theEffectiveDtm) { public void setEffectiveDtm(Date theEffectiveDtm) {
myEffectiveDtm = theEffectiveDtm; myEffectiveDtm = theEffectiveDtm;
} }
public Date getEffectiveDtm() { public Date getEffectiveDtm() {
return myEffectiveDtm; return myEffectiveDtm;
} }
public String getSubject() { public String getSubject() {
return mySubject; return mySubject;
} }
public String getIdentifier() { public String getIdentifier() {
return myIdentifier; return myIdentifier;
} }
} }

View File

@ -1,33 +0,0 @@
package ca.uhn.fhir.jpa.search.lastn.util;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
public class CodeSystemHash {
private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(0);
private static final byte[] DELIMITER_BYTES = "|".getBytes(Charsets.UTF_8);
static public long hashCodeSystem( String system, String code ) {
Hasher hasher = HASH_FUNCTION.newHasher();
addStringToHasher(hasher, system);
addStringToHasher(hasher, code);
HashCode hashCode = hasher.hash();
return hashCode.asLong();
}
static private void addStringToHasher(Hasher hasher, String next) {
if (next == null) {
hasher.putByte((byte) 0);
} else {
next = UrlUtil.escapeUrlParam(next);
byte[] bytes = next.getBytes(Charsets.UTF_8);
hasher.putBytes(bytes);
}
hasher.putBytes(DELIMITER_BYTES);
}
}

View File

@ -0,0 +1,26 @@
{
"mappings" : {
"ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity" : {
"properties" : {
"codeable_concept_id" : {
"type" : "keyword"
},
"codingcode" : {
"type" : "keyword"
},
"codingcode_system_hash" : {
"type" : "keyword"
},
"codingdisplay" : {
"type" : "keyword"
},
"codingsystem" : {
"type" : "keyword"
},
"text" : {
"type" : "keyword"
}
}
}
}
}

View File

@ -0,0 +1,50 @@
{
"mappings" : {
"ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity" : {
"properties" : {
"codeconceptid" : {
"type" : "keyword"
},
"codeconcepttext" : {
"type" : "text"
},
"codeconceptcodingcode" : {
"type" : "keyword"
},
"codeconceptcodingsystem" : {
"type" : "keyword"
},
"codeconceptcodingcode_system_hash" : {
"type" : "keyword"
},
"codeconceptcodingdisplay" : {
"type" : "keyword"
},
"categoryconcepttext" : {
"type" : "keyword"
},
"categoryconceptcodingcode" : {
"type" : "keyword"
},
"categoryconceptcodingsystem" : {
"type" : "keyword"
},
"categoryconceptcodingcode_system_hash" : {
"type" : "keyword"
},
"categoryconceptcodingdisplay" : {
"type" : "keyword"
},
"effectivedtm" : {
"type" : "date"
},
"identifier" : {
"type" : "keyword"
},
"subject" : {
"type" : "keyword"
}
}
}
}
}

View File

@ -52,14 +52,14 @@ public class FhirResourceDaoR4SearchLastNAsyncIT extends BaseR4SearchLastN {
mySmallerPreFetchThresholds.add(-1); mySmallerPreFetchThresholds.add(-1);
myDaoConfig.setSearchPreFetchThresholds(mySmallerPreFetchThresholds); myDaoConfig.setSearchPreFetchThresholds(mySmallerPreFetchThresholds);
SearchBuilder.setIsTest(true); SearchBuilder.setMaxPageSize50ForTest(true);
} }
@After @After
public void after() { public void after() {
myDaoConfig.setSearchPreFetchThresholds(originalPreFetchThresholds); myDaoConfig.setSearchPreFetchThresholds(originalPreFetchThresholds);
SearchBuilder.setIsTest(false); SearchBuilder.setMaxPageSize50ForTest(false);
} }
@Test @Test
@ -82,7 +82,7 @@ public class FhirResourceDaoR4SearchLastNAsyncIT extends BaseR4SearchLastN {
when(mySrd.getParameters()).thenReturn(requestParameters); when(mySrd.getParameters()).thenReturn(requestParameters);
// Set chunk size to 50 // Set chunk size to 50
SearchBuilder.setIsTest(true); SearchBuilder.setMaxPageSize50ForTest(true);
// Expand default fetch sizes to ensure all observations are returned in first page: // Expand default fetch sizes to ensure all observations are returned in first page:
List<Integer> myBiggerPreFetchThresholds = new ArrayList<>(); List<Integer> myBiggerPreFetchThresholds = new ArrayList<>();

View File

@ -23,7 +23,7 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseR4SearchLastN {
@After @After
public void resetMaximumPageSize() { public void resetMaximumPageSize() {
SearchBuilder.setIsTest(false); SearchBuilder.setMaxPageSize50ForTest(false);
} }
@Test @Test
@ -46,7 +46,7 @@ public class FhirResourceDaoR4SearchLastNIT extends BaseR4SearchLastN {
when(mySrd.getParameters()).thenReturn(requestParameters); when(mySrd.getParameters()).thenReturn(requestParameters);
// Set chunk size to 50 // Set chunk size to 50
SearchBuilder.setIsTest(true); SearchBuilder.setMaxPageSize50ForTest(true);
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
List<String> results = toUnqualifiedVersionlessIdValues(myObservationDao.observationsLastN(params, mockSrd(),null)); List<String> results = toUnqualifiedVersionlessIdValues(myObservationDao.observationsLastN(params, mockSrd(),null));

View File

@ -130,7 +130,6 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
myObservation.setCode(getObservationCode()); myObservation.setCode(getObservationCode());
//myObservationLastNIndexPersistR4Svc.indexObservation(myObservation);
testObservationPersist.indexObservation(myObservation); testObservationPersist.indexObservation(myObservation);
} }
@ -139,12 +138,12 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
// Add three CodeableConcepts for category // Add three CodeableConcepts for category
List<CodeableConcept> categoryConcepts = new ArrayList<>(); List<CodeableConcept> categoryConcepts = new ArrayList<>();
// Create three codings and first category CodeableConcept // Create three codings and first category CodeableConcept
List<Coding> category1 = new ArrayList<>(); List<Coding> category = new ArrayList<>();
CodeableConcept categoryCodeableConcept1 = new CodeableConcept().setText("Test Codeable Concept Field for first category"); CodeableConcept categoryCodeableConcept1 = new CodeableConcept().setText("Test Codeable Concept Field for first category");
category1.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE, "test-heart-rate display")); category.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE, "test-heart-rate display"));
category1.add(new Coding("http://myalternatecodes.org/fhir/observation-category", "test-alt-heart-rate", "test-alt-heart-rate display")); category.add(new Coding("http://myalternatecodes.org/fhir/observation-category", "test-alt-heart-rate", "test-alt-heart-rate display"));
category1.add(new Coding("http://mysecondaltcodes.org/fhir/observation-category", "test-2nd-alt-heart-rate", "test-2nd-alt-heart-rate display")); category.add(new Coding("http://mysecondaltcodes.org/fhir/observation-category", "test-2nd-alt-heart-rate", "test-2nd-alt-heart-rate display"));
categoryCodeableConcept1.setCoding(category1); categoryCodeableConcept1.setCoding(category);
categoryConcepts.add(categoryCodeableConcept1); categoryConcepts.add(categoryCodeableConcept1);
// Create three codings and second category CodeableConcept // Create three codings and second category CodeableConcept
List<Coding> category2 = new ArrayList<>(); List<Coding> category2 = new ArrayList<>();
@ -169,9 +168,6 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
// Create CodeableConcept for Code with three codings. // Create CodeableConcept for Code with three codings.
CodeableConcept codeableConceptField = new CodeableConcept().setText(SINGLE_OBSERVATION_CODE_TEXT); CodeableConcept codeableConceptField = new CodeableConcept().setText(SINGLE_OBSERVATION_CODE_TEXT);
codeableConceptField.addCoding(new Coding(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE, "test-code display")); codeableConceptField.addCoding(new Coding(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE, "test-code display"));
// TODO: Temporarily limit this to a single Observation Code coding until we sort out how to manage multiple codings
// codeableConceptField.addCoding(new Coding("http://myalternatecodes.org/fhir/observation-code", "test-alt-code", "test-alt-code display"));
// codeableConceptField.addCoding(new Coding("http://mysecondaltcodes.org/fhir/observation-code", "test-second-alt-code", "test-second-alt-code display"));
return codeableConceptField; return codeableConceptField;
} }
@ -263,8 +259,6 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
Date effectiveDtm = observationDate.getTime(); Date effectiveDtm = observationDate.getTime();
observation.setEffective(new DateTimeType(effectiveDtm)); observation.setEffective(new DateTimeType(effectiveDtm));
// myObservationLastNIndexPersistR4Svc.indexObservation(observation);
testObservationPersist.indexObservation(observation); testObservationPersist.indexObservation(observation);
} }
@ -279,7 +273,7 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
indexMultipleObservations(); indexMultipleObservations();
assertEquals(100, myResourceIndexedObservationLastNDao.count()); assertEquals(100, myResourceIndexedObservationLastNDao.count());
// Check that fifth observation for fifth patient has been indexed. // Check that fifth observation for fifth patient has been indexed.
ObservationIndexedSearchParamLastNEntity observation = myResourceIndexedObservationLastNDao.findForIdentifier("55"); ObservationIndexedSearchParamLastNEntity observation = myResourceIndexedObservationLastNDao.findByIdentifier("55");
assertNotNull(observation); assertNotNull(observation);
SearchParameterMap searchParameterMap = new SearchParameterMap(); SearchParameterMap searchParameterMap = new SearchParameterMap();
@ -295,12 +289,11 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
entity.setResourceType("Observation"); entity.setResourceType("Observation");
entity.setVersion(0L); entity.setVersion(0L);
// myObservationLastNIndexPersistR4Svc.deleteObservationIndex(entity);
testObservationPersist.deleteObservationIndex(entity); testObservationPersist.deleteObservationIndex(entity);
// Confirm that observation was deleted. // Confirm that observation was deleted.
assertEquals(99, myResourceIndexedObservationLastNDao.count()); assertEquals(99, myResourceIndexedObservationLastNDao.count());
observation = myResourceIndexedObservationLastNDao.findForIdentifier("55"); observation = myResourceIndexedObservationLastNDao.findByIdentifier("55");
assertNull(observation); assertNull(observation);
observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 200); observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 200);
@ -341,10 +334,9 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
updatedObservation.setCategory(getCategoryCode()); updatedObservation.setCategory(getCategoryCode());
updatedObservation.setCode(getObservationCode()); updatedObservation.setCode(getObservationCode());
// myObservationLastNIndexPersistR4Svc.indexObservation(updatedObservation);
testObservationPersist.indexObservation(updatedObservation); testObservationPersist.indexObservation(updatedObservation);
ObservationIndexedSearchParamLastNEntity updatedObservationEntity = myResourceIndexedObservationLastNDao.findForIdentifier(SINGLE_OBSERVATION_PID); ObservationIndexedSearchParamLastNEntity updatedObservationEntity = myResourceIndexedObservationLastNDao.findByIdentifier(SINGLE_OBSERVATION_PID);
assertEquals("1234", updatedObservationEntity.getSubject()); assertEquals("1234", updatedObservationEntity.getSubject());
assertEquals(newEffectiveDtm.getValue(), updatedObservationEntity.getEffectiveDtm()); assertEquals(newEffectiveDtm.getValue(), updatedObservationEntity.getEffectiveDtm());

View File

@ -54,7 +54,7 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
@After @After
public void after() throws IOException { public void after() throws IOException {
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_INDEX); elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.CODE_INDEX); elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
} }
@Test @Test
@ -307,18 +307,12 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
String codeableConceptId1 = UUID.randomUUID().toString(); String codeableConceptId1 = UUID.randomUUID().toString();
CodeableConcept codeableConceptField1 = new CodeableConcept().setText("Test Codeable Concept Field for First Code"); CodeableConcept codeableConceptField1 = new CodeableConcept().setText("Test Codeable Concept Field for First Code");
codeableConceptField1.addCoding(new Coding("http://mycodes.org/fhir/observation-code", "test-code-1", "test-code-1 display")); codeableConceptField1.addCoding(new Coding("http://mycodes.org/fhir/observation-code", "test-code-1", "test-code-1 display"));
// TODO: uncomment the following once there is a solution to supporting multiple codings for Observation Code
// codeableConceptField1.addCoding(new Coding("http://myalternatecodes.org/fhir/observation-code", "test-alt-code-1", "test-alt-code-1 display"));
// codeableConceptField1.addCoding(new Coding("http://mysecondaltcodes.org/fhir/observation-code", "test-second-alt-code-1", "test-second-alt-code-1 display"));
CodeJson codeJson1 = new CodeJson(codeableConceptField1, codeableConceptId1); CodeJson codeJson1 = new CodeJson(codeableConceptField1, codeableConceptId1);
String codeJson1Document = ourMapperNonPrettyPrint.writeValueAsString(codeJson1); String codeJson1Document = ourMapperNonPrettyPrint.writeValueAsString(codeJson1);
String codeableConceptId2 = UUID.randomUUID().toString(); String codeableConceptId2 = UUID.randomUUID().toString();
CodeableConcept codeableConceptField2 = new CodeableConcept().setText("Test Codeable Concept Field for Second Code"); CodeableConcept codeableConceptField2 = new CodeableConcept().setText("Test Codeable Concept Field for Second Code");
codeableConceptField2.addCoding(new Coding("http://mycodes.org/fhir/observation-code", "test-code-2", "test-code-2 display")); codeableConceptField2.addCoding(new Coding("http://mycodes.org/fhir/observation-code", "test-code-2", "test-code-2 display"));
// TODO: uncomment the following once there is a solution to supporting multiple codings for Observation Code
// codeableConceptField2.addCoding(new Coding("http://myalternatecodes.org/fhir/observation-code", "test-alt-code-2", "test-alt-code-2 display"));
// codeableConceptField2.addCoding(new Coding("http://mysecondaltcodes.org/fhir/observation-code", "test-second-alt-code-2", "test-second-alt-code-2 display"));
CodeJson codeJson2 = new CodeJson(codeableConceptField2, codeableConceptId2); CodeJson codeJson2 = new CodeJson(codeableConceptField2, codeableConceptId2);
String codeJson2Document = ourMapperNonPrettyPrint.writeValueAsString(codeJson2); String codeJson2Document = ourMapperNonPrettyPrint.writeValueAsString(codeJson2);
@ -357,12 +351,12 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
observationJson.setCategories(categoryConcepts1); observationJson.setCategories(categoryConcepts1);
observationJson.setCode(codeableConceptField1); observationJson.setCode(codeableConceptField1);
observationJson.setCode_concept_id(codeableConceptId1); observationJson.setCode_concept_id(codeableConceptId1);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.CODE_INDEX, codeableConceptId1, codeJson1Document, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE)); assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX, codeableConceptId1, codeJson1Document, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE));
} else { } else {
observationJson.setCategories(categoryConcepts2); observationJson.setCategories(categoryConcepts2);
observationJson.setCode(codeableConceptField2); observationJson.setCode(codeableConceptField2);
observationJson.setCode_concept_id(codeableConceptId2); observationJson.setCode_concept_id(codeableConceptId2);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.CODE_INDEX, codeableConceptId2, codeJson2Document, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE)); assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX, codeableConceptId2, codeJson2Document, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE));
} }
Calendar observationDate = new GregorianCalendar(); Calendar observationDate = new GregorianCalendar();

View File

@ -1,27 +1,37 @@
package ca.uhn.fhir.jpa.search.lastn; package ca.uhn.fhir.jpa.search.lastn;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import ca.uhn.fhir.jpa.search.lastn.config.TestElasticsearchConfig; import ca.uhn.fhir.jpa.search.lastn.config.TestElasticsearchConfig;
import ca.uhn.fhir.jpa.search.lastn.json.CodeJson; import ca.uhn.fhir.jpa.search.lastn.json.CodeJson;
import ca.uhn.fhir.jpa.search.lastn.json.ObservationJson; import ca.uhn.fhir.jpa.search.lastn.json.ObservationJson;
import ca.uhn.fhir.jpa.search.lastn.util.CodeSystemHash;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.dstu2.resource.Observation; import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.junit.*; import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -68,12 +78,6 @@ public class LastNElasticsearchSvcSingleObservationIT {
final String CODEFIRSTCODINGSYSTEM = "http://mycodes.org/fhir/observation-code"; final String CODEFIRSTCODINGSYSTEM = "http://mycodes.org/fhir/observation-code";
final String CODEFIRSTCODINGCODE = "test-code"; final String CODEFIRSTCODINGCODE = "test-code";
final String CODEFIRSTCODINGDISPLAY = "test-code display"; final String CODEFIRSTCODINGDISPLAY = "test-code display";
// final String CODESECONDCODINGSYSTEM = "http://myalternatecodes.org/fhir/observation-code";
// final String CODESECONDCODINGCODE = "test-alt-code";
// final String CODESECONDCODINGDISPLAY = "test-alt-code display";
// final String CODETHIRDCODINGSYSTEM = "http://mysecondaltcodes.org/fhir/observation-code";
// final String CODETHIRDCODINGCODE = "test-second-alt-code";
// final String CODETHIRDCODINGDISPLAY = "test-second-alt-code display";
final FhirContext myFhirContext = FhirContext.forR4(); final FhirContext myFhirContext = FhirContext.forR4();
@ -89,7 +93,7 @@ public class LastNElasticsearchSvcSingleObservationIT {
@After @After
public void after() throws IOException { public void after() throws IOException {
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_INDEX); elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.CODE_INDEX); elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
} }
@Test @Test
@ -209,32 +213,6 @@ public class LastNElasticsearchSvcSingleObservationIT {
String code_concept_text_values = observation.getCode_concept_text(); String code_concept_text_values = observation.getCode_concept_text();
assertEquals(OBSERVATIONCODETEXT, code_concept_text_values); assertEquals(OBSERVATIONCODETEXT, code_concept_text_values);
// TODO: Temporary changes until find a solution for addressing Observation Code with multiple codings.
/*
List<String> code_coding_systems = observation.getCode_coding_system();
assertEquals(3,code_coding_systems.size());
assertEquals(CODEFIRSTCODINGSYSTEM, code_coding_systems.get(0));
assertEquals(CODESECONDCODINGSYSTEM, code_coding_systems.get(1));
assertEquals(CODETHIRDCODINGSYSTEM, code_coding_systems.get(2));
List<String> code_coding_codes = observation.getCode_coding_code();
assertEquals(3, code_coding_codes.size());
assertEquals(CODEFIRSTCODINGCODE, code_coding_codes.get(0));
assertEquals(CODESECONDCODINGCODE, code_coding_codes.get(1));
assertEquals(CODETHIRDCODINGCODE, code_coding_codes.get(2));
List<String> code_coding_display = observation.getCode_coding_display();
assertEquals(3, code_coding_display.size());
assertEquals(CODEFIRSTCODINGDISPLAY, code_coding_display.get(0));
assertEquals(CODESECONDCODINGDISPLAY, code_coding_display.get(1));
assertEquals(CODETHIRDCODINGDISPLAY, code_coding_display.get(2));
List<String> code_coding_code_system_hash = observation.getCode_coding_code_system_hash();
assertEquals(3, code_coding_code_system_hash.size());
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE)), code_coding_code_system_hash.get(0));
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODESECONDCODINGSYSTEM, CODESECONDCODINGCODE)), code_coding_code_system_hash.get(1));
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODETHIRDCODINGSYSTEM, CODETHIRDCODINGCODE)), code_coding_code_system_hash.get(2));
*/
String code_coding_systems = observation.getCode_coding_system(); String code_coding_systems = observation.getCode_coding_system();
assertEquals(CODEFIRSTCODINGSYSTEM, code_coding_systems); assertEquals(CODEFIRSTCODINGSYSTEM, code_coding_systems);
@ -258,36 +236,20 @@ public class LastNElasticsearchSvcSingleObservationIT {
assertEquals(OBSERVATIONCODETEXT, persistedCodeConceptText); assertEquals(OBSERVATIONCODETEXT, persistedCodeConceptText);
List<String> persistedCodeCodingSystems = persistedObservationCode.getCoding_system(); List<String> persistedCodeCodingSystems = persistedObservationCode.getCoding_system();
// TODO: Temporary changes until find a solution for addressing Observation Code with multiple codings.
// assertEquals(3,persistedCodeCodingSystems.size());
assertEquals(1, persistedCodeCodingSystems.size()); assertEquals(1, persistedCodeCodingSystems.size());
assertEquals(CODEFIRSTCODINGSYSTEM, persistedCodeCodingSystems.get(0)); assertEquals(CODEFIRSTCODINGSYSTEM, persistedCodeCodingSystems.get(0));
// assertEquals(CODESECONDCODINGSYSTEM, persistedCodeCodingSystems.get(1));
// assertEquals(CODETHIRDCODINGSYSTEM, persistedCodeCodingSystems.get(2));
List<String> persistedCodeCodingCodes = persistedObservationCode.getCoding_code(); List<String> persistedCodeCodingCodes = persistedObservationCode.getCoding_code();
// TODO: Temporary changes until find a solution for addressing Observation Code with multiple codings.
// assertEquals(3, persistedCodeCodingCodes.size());
assertEquals(1, persistedCodeCodingCodes.size()); assertEquals(1, persistedCodeCodingCodes.size());
assertEquals(CODEFIRSTCODINGCODE, persistedCodeCodingCodes.get(0)); assertEquals(CODEFIRSTCODINGCODE, persistedCodeCodingCodes.get(0));
// assertEquals(CODESECONDCODINGCODE, persistedCodeCodingCodes.get(1));
// assertEquals(CODETHIRDCODINGCODE, persistedCodeCodingCodes.get(2));
List<String> persistedCodeCodingDisplays = persistedObservationCode.getCoding_display(); List<String> persistedCodeCodingDisplays = persistedObservationCode.getCoding_display();
// TODO: Temporary changes until find a solution for addressing Observation Code with multiple codings.
// assertEquals(3, persistedCodeCodingDisplays.size());
assertEquals(1, persistedCodeCodingDisplays.size()); assertEquals(1, persistedCodeCodingDisplays.size());
assertEquals(CODEFIRSTCODINGDISPLAY, persistedCodeCodingDisplays.get(0)); assertEquals(CODEFIRSTCODINGDISPLAY, persistedCodeCodingDisplays.get(0));
// assertEquals(CODESECONDCODINGDISPLAY, persistedCodeCodingDisplays.get(1));
// assertEquals(CODETHIRDCODINGDISPLAY, persistedCodeCodingDisplays.get(2));
List<String> persistedCodeCodingCodeSystemHashes = persistedObservationCode.getCoding_code_system_hash(); List<String> persistedCodeCodingCodeSystemHashes = persistedObservationCode.getCoding_code_system_hash();
// TODO: Temporary changes until find a solution for addressing Observation Code with multiple codings.
// assertEquals(3, persistedCodeCodingCodeSystemHashes.size());
assertEquals(1, persistedCodeCodingCodeSystemHashes.size()); assertEquals(1, persistedCodeCodingCodeSystemHashes.size());
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE)), persistedCodeCodingCodeSystemHashes.get(0)); assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE)), persistedCodeCodingCodeSystemHashes.get(0));
// assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODESECONDCODINGSYSTEM, CODESECONDCODINGCODE)), persistedCodeCodingCodeSystemHashes.get(1));
// assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODETHIRDCODINGSYSTEM, CODETHIRDCODINGCODE)), persistedCodeCodingCodeSystemHashes.get(2));
} }
@ -330,9 +292,6 @@ public class LastNElasticsearchSvcSingleObservationIT {
indexedObservation.setCode_concept_id(OBSERVATIONSINGLECODEID); indexedObservation.setCode_concept_id(OBSERVATIONSINGLECODEID);
CodeableConcept codeableConceptField = new CodeableConcept().setText(OBSERVATIONCODETEXT); CodeableConcept codeableConceptField = new CodeableConcept().setText(OBSERVATIONCODETEXT);
codeableConceptField.addCoding(new Coding(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE, CODEFIRSTCODINGDISPLAY)); codeableConceptField.addCoding(new Coding(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE, CODEFIRSTCODINGDISPLAY));
// TODO: Temporary changes until find a solution for addressing Observation Code with multiple codings.
// codeableConceptField.addCoding(new Coding(CODESECONDCODINGSYSTEM, CODESECONDCODINGCODE, CODESECONDCODINGDISPLAY));
// codeableConceptField.addCoding(new Coding(CODETHIRDCODINGSYSTEM, CODETHIRDCODINGCODE, CODETHIRDCODINGDISPLAY));
indexedObservation.setCode(codeableConceptField); indexedObservation.setCode(codeableConceptField);
String observationDocument = ourMapperNonPrettyPrint.writeValueAsString(indexedObservation); String observationDocument = ourMapperNonPrettyPrint.writeValueAsString(indexedObservation);
@ -340,7 +299,7 @@ public class LastNElasticsearchSvcSingleObservationIT {
CodeJson observationCode = new CodeJson(codeableConceptField, OBSERVATIONSINGLECODEID); CodeJson observationCode = new CodeJson(codeableConceptField, OBSERVATIONSINGLECODEID);
String codeDocument = ourMapperNonPrettyPrint.writeValueAsString(observationCode); String codeDocument = ourMapperNonPrettyPrint.writeValueAsString(observationCode);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.CODE_INDEX, OBSERVATIONSINGLECODEID, codeDocument, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE)); assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX, OBSERVATIONSINGLECODEID, codeDocument, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE));
try { try {
Thread.sleep(1000L); Thread.sleep(1000L);

View File

@ -47,9 +47,4 @@ public class TestElasticsearchConfig {
return embeddedElastic; return embeddedElastic;
} }
// @PreDestroy
// public void stop() {
// embeddedElasticSearch().stop();
// }
} }

View File

@ -24,12 +24,9 @@ public class ObservationIndexedCodeCodeableConceptEntity {
@Column(name = "CODEABLE_CONCEPT_TEXT", nullable = true, length = MAX_LENGTH) @Column(name = "CODEABLE_CONCEPT_TEXT", nullable = true, length = MAX_LENGTH)
private String myCodeableConceptText; private String myCodeableConceptText;
// TODO: Make coding a Collection. Need to first figure out how to maintain this over time.
@IndexedEmbedded(depth=2, prefix = "coding") @IndexedEmbedded(depth=2, prefix = "coding")
// @OneToMany(mappedBy = "myCodeableConceptId", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
@JoinColumn(name = "CODEABLE_CONCEPT_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_CONCEPT_CODE")) @JoinColumn(name = "CODEABLE_CONCEPT_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_CONCEPT_CODE"))
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
// private Set<ObservationIndexedCodeCodingEntity> myObservationIndexedCodeCodingEntitySet;
private ObservationIndexedCodeCodingEntity myObservationIndexedCodeCodingEntity; private ObservationIndexedCodeCodingEntity myObservationIndexedCodeCodingEntity;
public ObservationIndexedCodeCodeableConceptEntity() { public ObservationIndexedCodeCodeableConceptEntity() {
@ -42,10 +39,6 @@ public class ObservationIndexedCodeCodeableConceptEntity {
} }
public void addCoding(ObservationIndexedCodeCodingEntity theObservationIndexedCodeCodingEntity) { public void addCoding(ObservationIndexedCodeCodingEntity theObservationIndexedCodeCodingEntity) {
// if (myObservationIndexedCodeCodingEntitySet == null) {
// myObservationIndexedCodeCodingEntitySet = new HashSet<>();
// }
// myObservationIndexedCodeCodingEntitySet.add(theObservationIndexedCodeCodingEntity);
myObservationIndexedCodeCodingEntity = theObservationIndexedCodeCodingEntity; myObservationIndexedCodeCodingEntity = theObservationIndexedCodeCodingEntity;
} }
@ -65,6 +58,4 @@ public class ObservationIndexedCodeCodeableConceptEntity {
myCodeableConceptText = theCodeableConceptText; myCodeableConceptText = theCodeableConceptText;
} }
} }

View File

@ -13,36 +13,31 @@ public class ObservationIndexedCodeCodingEntity {
public static final int MAX_LENGTH = 200; public static final int MAX_LENGTH = 200;
// TODO: Fix this to allow multiple codings for observation code
// @Id
// @SequenceGenerator(name = "SEQ_CODING_FIELD", sequenceName = "SEQ_CODING_FIELD")
// @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CODING_FIELD")
// private Long myId;
@Id @Id
@Column(name="CODEABLE_CONCEPT_ID", length = MAX_LENGTH) @Column(name = "CODEABLE_CONCEPT_ID", length = MAX_LENGTH)
private String myCodeableConceptId; private String myCodeableConceptId;
@Field (name = "code", analyze = Analyze.NO) @Field(name = "code", analyze = Analyze.NO)
private String myCode; private String myCode;
@Field (name = "system", analyze = Analyze.NO) @Field(name = "system", analyze = Analyze.NO)
private String mySystem; private String mySystem;
@Field (name = "code_system_hash", analyze = Analyze.NO) @Field(name = "code_system_hash", analyze = Analyze.NO)
private String myCodeSystemHash; private String myCodeSystemHash;
@Field (name = "display") @Field(name = "display")
private String myDisplay; private String myDisplay;
public ObservationIndexedCodeCodingEntity() {} public ObservationIndexedCodeCodingEntity() {
}
public ObservationIndexedCodeCodingEntity(String theSystem, String theCode, String theDisplay, String theCodeableConceptId) { public ObservationIndexedCodeCodingEntity(String theSystem, String theCode, String theDisplay, String theCodeableConceptId) {
myCode = theCode; myCode = theCode;
mySystem = theSystem; mySystem = theSystem;
myCodeSystemHash = String.valueOf(CodeSystemHash.hashCodeSystem(theSystem, theCode)); myCodeSystemHash = String.valueOf(CodeSystemHash.hashCodeSystem(theSystem, theCode));
myDisplay = theDisplay; myDisplay = theDisplay;
myCodeableConceptId = theCodeableConceptId; myCodeableConceptId = theCodeableConceptId;
} }
} }

View File

@ -10,32 +10,47 @@ public class LastNParameterHelper {
if (theParamName == null) { if (theParamName == null) {
return false; return false;
} }
if (theContext.getVersion().getVersion() == FhirVersionEnum.R5) {
if (theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_SUBJECT) || theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_PATIENT) FhirVersionEnum version = theContext.getVersion().getVersion();
|| theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_CATEGORY) || theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_CODE)) {
return true; if (isR5(version) && isLastNParameterR5(theParamName)) {
} else { return true;
return false; } else if (isR4(version) && isLastNParameterR4(theParamName)) {
} return true;
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.R4) { } else if (isDstu3(version) && isLastNParameterDstu3(theParamName)) {
if (theParamName.equals(org.hl7.fhir.r4.model.Observation.SP_SUBJECT) || theParamName.equals(org.hl7.fhir.r4.model.Observation.SP_PATIENT) return true;
|| theParamName.equals(org.hl7.fhir.r4.model.Observation.SP_CATEGORY) || theParamName.equals(org.hl7.fhir.r4.model.Observation.SP_CODE)) {
return true;
} else {
return false;
}
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU3) {
if (theParamName.equals(org.hl7.fhir.dstu3.model.Observation.SP_SUBJECT) || theParamName.equals(org.hl7.fhir.dstu3.model.Observation.SP_PATIENT)
|| theParamName.equals(org.hl7.fhir.dstu3.model.Observation.SP_CATEGORY) || theParamName.equals(org.hl7.fhir.dstu3.model.Observation.SP_CODE)) {
return true;
} else {
return false;
}
} else { } else {
throw new InvalidRequestException("$lastn operation is not implemented for FHIR Version " + theContext.getVersion().getVersion().getFhirVersionString()); return false;
} }
} }
private static boolean isDstu3(FhirVersionEnum theVersion) {
return (theVersion == FhirVersionEnum.DSTU3);
}
private static boolean isR4(FhirVersionEnum theVersion) {
return (theVersion == FhirVersionEnum.R4);
}
private static boolean isR5(FhirVersionEnum theVersion) {
return (theVersion == FhirVersionEnum.R5);
}
private static boolean isLastNParameterDstu3(String theParamName) {
return (theParamName.equals(org.hl7.fhir.dstu3.model.Observation.SP_SUBJECT) || theParamName.equals(org.hl7.fhir.dstu3.model.Observation.SP_PATIENT)
|| theParamName.equals(org.hl7.fhir.dstu3.model.Observation.SP_CATEGORY) || theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_CODE));
}
private static boolean isLastNParameterR4(String theParamName) {
return (theParamName.equals(org.hl7.fhir.r4.model.Observation.SP_SUBJECT) || theParamName.equals(org.hl7.fhir.r4.model.Observation.SP_PATIENT)
|| theParamName.equals(org.hl7.fhir.r4.model.Observation.SP_CATEGORY) || theParamName.equals(org.hl7.fhir.r4.model.Observation.SP_CODE));
}
private static boolean isLastNParameterR5(String theParamName) {
return (theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_SUBJECT) || theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_PATIENT)
|| theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_CATEGORY) || theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_CODE));
}
public static String getSubjectParamName(FhirContext theContext) { public static String getSubjectParamName(FhirContext theContext) {
if (theContext.getVersion().getVersion() == FhirVersionEnum.R5) { if (theContext.getVersion().getVersion() == FhirVersionEnum.R5) {
return org.hl7.fhir.r5.model.Observation.SP_SUBJECT; return org.hl7.fhir.r5.model.Observation.SP_SUBJECT;