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> {
/**
* 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);
}

View File

@ -22,25 +22,50 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
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.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.*;
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 org.hl7.fhir.instance.model.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TreeMap;
public abstract class BaseHapiFhirResourceDaoObservation<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> implements IFhirResourceDaoObservation<T> {
@Autowired
ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc;
@Autowired
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) {
if (!isPagingProviderDatabaseBacked(theRequestDetails)) {
theSearchParameterMap.setLoadSynchronous(true);

View File

@ -39,40 +39,46 @@ public class ObservationLastNIndexPersistSvc {
public void indexObservation(IBaseResource theResource) {
String subjectId = null;
List<IBase> subjectReferenceElement = mySearchParameterExtractor.extractValues("Observation.subject", theResource);
if (subjectReferenceElement.size() == 1) {
PathAndRef subjectPathAndRef = mySearchParameterExtractor.extractReferenceLinkFromResource(subjectReferenceElement.get(0), "Observation.subject");
if (subjectPathAndRef != null) {
IBaseReference subjectReference = subjectPathAndRef.getRef();
if (subjectReference != null) {
subjectId = subjectReference.getReferenceElement().getValue();
}
}
}
String subjectId = subjectReferenceElement.stream()
.map(refElement -> mySearchParameterExtractor.extractReferenceLinkFromResource(refElement, "Observation.subject"))
.filter(Objects::nonNull)
.map(PathAndRef::getRef)
.filter(Objects::nonNull)
.map(subjectRef -> subjectRef.getReferenceElement().getValue())
.filter(Objects::nonNull)
.findFirst().orElse(null);
Date effectiveDtm = null;
List<IBase> effectiveDateElement = mySearchParameterExtractor.extractValues("Observation.effective", theResource);
if (effectiveDateElement.size() == 1) {
if (effectiveDateElement.size() > 0) {
effectiveDtm = mySearchParameterExtractor.extractDateFromResource(effectiveDateElement.get(0), "Observation.effective");
}
// Build CodeableConcept entity for Observation.Code.
List<IBase> observationCodeCodeableConcepts = mySearchParameterExtractor.extractValues("Observation.code", theResource);
// Only index for lastn if Observation has a subject, effective date/time and code
if (subjectId == null || effectiveDtm == null || observationCodeCodeableConcepts.size() == 0) {
// Only index for lastn if Observation has a code
if (observationCodeCodeableConcepts.size() == 0) {
return;
}
List<IBase> observationCategoryCodeableConcepts = mySearchParameterExtractor.extractValues("Observation.category", theResource);
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:
boolean observationIndexUpdate = false;
ObservationIndexedSearchParamLastNEntity indexedObservation = null;
if (resourcePID != null) {
indexedObservation = myResourceIndexedObservationLastNDao.findForIdentifier(resourcePID);
indexedObservation = myResourceIndexedObservationLastNDao.findByIdentifier(resourcePID);
}
if (indexedObservation == null) {
indexedObservation = new ObservationIndexedSearchParamLastNEntity();
@ -80,27 +86,46 @@ public class ObservationLastNIndexPersistSvc {
observationIndexUpdate = true;
}
indexedObservation.setEffectiveDtm(effectiveDtm);
indexedObservation.setEffectiveDtm(theEffectiveDtm);
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
boolean observationCodeUpdate = false;
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();
}
Optional<String> existingObservationCodeNormalizedId = getCodeCodeableConceptIdIfExists(theObservationCodeCodeableConcepts.get(0));
// 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
List<IBase> observationCategoryCodeableConcepts = mySearchParameterExtractor.extractValues("Observation.category", theResource);
Set<ObservationIndexedCategoryCodeableConceptEntity> categoryCodeableConceptEntities = new HashSet<>();
for (IBase categoryCodeableConcept : observationCategoryCodeableConcepts) {
// Build CodeableConcept entities for each category CodeableConcept
@ -108,20 +133,6 @@ public class ObservationLastNIndexPersistSvc {
}
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) {
@ -151,29 +162,32 @@ public class ObservationLastNIndexPersistSvc {
return codeCodeableConcept;
}
private String getCodeCodeableConceptIdIfExists(IBase theValue) {
private Optional<String> getCodeCodeableConceptIdIfExists(IBase theValue) {
List<IBase> codings = mySearchParameterExtractor.getCodingsFromCodeableConcept(theValue);
String codeCodeableConceptId = null;
Optional<String> codeCodeableConceptIdOptional = Optional.empty();
for (IBase nextCoding : codings) {
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);
if (param != null) {
String system = param.getSystem();
String code = param.getValue();
String text = mySearchParameterExtractor.getDisplayTextForCoding(nextCoding);
if (code != null && system != null) {
codeCodeableConceptId = myObservationIndexedCodeCodingSearchParamDao.findForCodeAndSystem(code, system);
codeCodeableConceptIdOptional = Optional.ofNullable(myObservationIndexedCodeCodingSearchParamDao.findByCodeAndSystem(code, system));
} else {
codeCodeableConceptId = myObservationIndexedCodeCodingSearchParamDao.findForDisplay(text);
codeCodeableConceptIdOptional = Optional.ofNullable(myObservationIndexedCodeCodingSearchParamDao.findByDisplay(text));
}
if (codeCodeableConceptId != null) {
if (codeCodeableConceptIdOptional.isPresent()) {
break;
}
}
}
return codeCodeableConceptId;
return codeCodeableConceptIdOptional;
}
private ObservationIndexedCategoryCodingEntity getCategoryCoding(IBase theValue) {
@ -205,7 +219,7 @@ public class ObservationLastNIndexPersistSvc {
}
public void deleteObservationIndex(IBasePersistedResource theEntity) {
ObservationIndexedSearchParamLastNEntity deletedObservationLastNEntity = myResourceIndexedObservationLastNDao.findForIdentifier(theEntity.getIdDt().getIdPart());
ObservationIndexedSearchParamLastNEntity deletedObservationLastNEntity = myResourceIndexedObservationLastNDao.findByIdentifier(theEntity.getIdDt().getIdPart());
if (deletedObservationLastNEntity != null) {
myEntityManager.remove(deletedObservationLastNEntity);
}

View File

@ -131,7 +131,7 @@ public class SearchBuilder implements ISearchBuilder {
// NB: keep public
public static final int MAXIMUM_PAGE_SIZE = 800;
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 Logger ourLog = LoggerFactory.getLogger(SearchBuilder.class);
@ -184,15 +184,15 @@ public class SearchBuilder implements ISearchBuilder {
}
public static int getMaximumPageSize() {
if (myIsTest) {
if (myUseMaxPageSize50ForTest) {
return MAXIMUM_PAGE_SIZE_FOR_TESTING;
} else {
return MAXIMUM_PAGE_SIZE;
}
}
public static void setIsTest(boolean theIsTest) {
myIsTest = theIsTest;
public static void setMaxPageSize50ForTest(boolean theIsTest) {
myUseMaxPageSize50ForTest = theIsTest;
}
@Override

View File

@ -14,13 +14,13 @@ public interface IObservationIndexedCodeCodingSearchParamDao extends JpaReposito
"WHERE t.myCode = :code " +
"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("" +
"SELECT t.myCodeableConceptId FROM ObservationIndexedCodeCodingEntity t " +
"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 " +
"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
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) {
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.
Observation observation = (Observation) theResource;
myObservationLastNIndexPersistSvc.indexObservation(observation);
} else {
myObservationLastNIndexPersistSvc.deleteObservationIndex(theEntity);
}
}
return retVal;
return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
theCreateNewHistoryEntry);
}
}

View File

@ -78,21 +78,11 @@ public class FhirResourceDaoObservationR4 extends BaseHapiFhirResourceDaoObserva
}
@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) {
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.
Observation observation = (Observation) theResource;
myObservationLastNIndexPersistSvc.indexObservation(observation);
} else {
myObservationLastNIndexPersistSvc.deleteObservationIndex(retVal);
}
}
return retVal;
return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
theCreateNewHistoryEntry);
}
}

View File

@ -71,21 +71,11 @@ public class FhirResourceDaoObservationR5 extends BaseHapiFhirResourceDaoObserva
}
@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) {
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.
Observation observation = (Observation) theResource;
myObservationLastNIndexPersistSvc.indexObservation(observation);
} else {
myObservationLastNIndexPersistSvc.deleteObservationIndex(theEntity);
}
}
return retVal;
return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
theCreateNewHistoryEntry);
}
}

View File

@ -1,22 +1,22 @@
package ca.uhn.fhir.jpa.search.lastn;
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.util.LastNParameterHelper;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.ReferenceParam;
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 com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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.CreateIndexResponse;
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.IndexResponse;
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.Aggregations;
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.Terms;
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.sort.SortOrder;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
@ -52,9 +57,11 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class ElasticsearchSvcImpl implements IElasticsearchSvc {
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 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;
@ -63,6 +70,7 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
private final String GROUP_BY_SUBJECT = "group_by_subject";
private final String GROUP_BY_SYSTEM = "group_by_system";
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) {
@ -70,106 +78,41 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
try {
createObservationIndexIfMissing();
createCodeIndexIfMissing();
createObservationCodeIndexIfMissing();
} catch (IOException 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 {
if (indexExists(OBSERVATION_INDEX)) {
return;
}
String observationMapping = "{\n" +
" \"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";
String observationMapping = getIndexSchema(OBSERVATION_INDEX_SCHEMA_FILE);
if (!createIndex(OBSERVATION_INDEX, observationMapping)) {
throw new RuntimeException("Failed to create observation index");
}
}
private void createCodeIndexIfMissing() throws IOException {
if (indexExists(CODE_INDEX)) {
private void createObservationCodeIndexIfMissing() throws IOException {
if (indexExists(OBSERVATION_CODE_INDEX)) {
return;
}
String codeMapping = "{\n" +
" \"mappings\" : {\n" +
" \"ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity\" : {\n" +
" \"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");
String observationCodeMapping = getIndexSchema(OBSERVATION_CODE_INDEX_SCHEMA_FILE);
if (!createIndex(OBSERVATION_CODE_INDEX, observationCodeMapping)) {
throw new RuntimeException("Failed to create observation code index");
}
}
@ -190,7 +133,6 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
@Override
public List<String> executeLastN(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext, Integer theMaxResultsToFetch) {
String OBSERVATION_IDENTIFIER_FIELD_NAME = "identifier";
String[] topHitsInclude = {OBSERVATION_IDENTIFIER_FIELD_NAME};
return buildAndExecuteSearch(theSearchParameterMap, theFhirContext, topHitsInclude,
ObservationJson::getIdentifier, theMaxResultsToFetch);
@ -557,7 +499,7 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
@VisibleForTesting
List<CodeJson> queryAllIndexedObservationCodes() throws IOException {
SearchRequest codeSearchRequest = new SearchRequest(CODE_INDEX);
SearchRequest codeSearchRequest = new SearchRequest(OBSERVATION_CODE_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// Query
searchSourceBuilder.query(QueryBuilders.matchAllQuery());

View File

@ -6,5 +6,13 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import java.util.List;
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);
}

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.search.lastn.json;
* #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.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.jpa.search.lastn.json;
* #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.JsonInclude;
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)
public class ObservationJson {
@JsonProperty(value = "identifier", required = true)
private String myIdentifier;
@JsonProperty(value = "identifier", required = true)
private String myIdentifier;
@JsonProperty(value = "subject", required = true)
private String mySubject;
@JsonProperty(value = "subject", required = true)
private String mySubject;
@JsonProperty(value = "categoryconcepttext", required = false)
private List<String> myCategory_concept_text = new ArrayList<>();
@JsonProperty(value = "categoryconcepttext", required = false)
private List<String> myCategory_concept_text = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingcode", required = false)
private List<List<String>> myCategory_coding_code = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingcode", required = false)
private List<List<String>> myCategory_coding_code = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingcode_system_hash", required = false)
private List<List<String>> myCategory_coding_code_system_hash = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingcode_system_hash", required = false)
private List<List<String>> myCategory_coding_code_system_hash = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingdisplay", required = false)
private List<List<String>> myCategory_coding_display = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingdisplay", required = false)
private List<List<String>> myCategory_coding_display = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingsystem", required = false)
private List<List<String>> myCategory_coding_system = new ArrayList<>();
@JsonProperty(value = "categoryconceptcodingsystem", required = false)
private List<List<String>> myCategory_coding_system = new ArrayList<>();
@JsonProperty(value = "codeconceptid", required = false)
private String myCode_concept_id;
@JsonProperty(value = "codeconceptid", required = false)
private String myCode_concept_id;
@JsonProperty(value = "codeconcepttext", required = false)
private String myCode_concept_text;
@JsonProperty(value = "codeconcepttext", required = false)
private String myCode_concept_text;
@JsonProperty(value = "codeconceptcodingcode", required = false)
// TODO: Temporary change until sort out how to deal with multiple observation code codings
// private List<String> myCode_coding_code = new ArrayList<>();
private String myCode_coding_code;
@JsonProperty(value = "codeconceptcodingcode", required = false)
private String myCode_coding_code;
@JsonProperty(value = "codeconceptcodingcode_system_hash", required = false)
// TODO: Temporary change until sort out how to deal with multiple observation code codings
// private List<String> myCode_coding_code_system_hash = new ArrayList<>();
private String myCode_coding_code_system_hash;
@JsonProperty(value = "codeconceptcodingcode_system_hash", required = false)
private String myCode_coding_code_system_hash;
@JsonProperty(value = "codeconceptcodingdisplay", required = false)
// TODO: Temporary change until sort out how to deal with multiple observation code codings
// private List<String> myCode_coding_display = new ArrayList<>();
private String myCode_coding_display;
@JsonProperty(value = "codeconceptcodingdisplay", required = false)
private String myCode_coding_display;
@JsonProperty(value = "codeconceptcodingsystem", required = false)
// TODO: Temporary change until sort out how to deal with multiple observation code codings
// private List<String> myCode_coding_system = new ArrayList<>();
private String myCode_coding_system;
@JsonProperty(value = "codeconceptcodingsystem", required = false)
private String myCode_coding_system;
@JsonProperty(value = "effectivedtm", required = true)
private Date myEffectiveDtm;
@JsonProperty(value = "effectivedtm", required = true)
private Date myEffectiveDtm;
public ObservationJson() {}
public ObservationJson() {
}
public void setIdentifier(String theIdentifier) {
myIdentifier = theIdentifier;
}
public void setIdentifier(String theIdentifier) {
myIdentifier = theIdentifier;
}
public void setSubject(String theSubject) {
mySubject = theSubject;
}
public void setSubject(String theSubject) {
mySubject = theSubject;
}
public void setCategories(List<CodeableConcept> theCategories) {
for (CodeableConcept theConcept : theCategories) {
myCategory_concept_text.add(theConcept.getText());
List<String> coding_code_system_hashes = new ArrayList<>();
List<String> coding_codes = new ArrayList<>();
List<String> coding_displays = new ArrayList<>();
List<String> coding_systems = new ArrayList<>();
for (Coding theCategoryCoding : theConcept.getCoding()) {
coding_code_system_hashes.add(String.valueOf(CodeSystemHash.hashCodeSystem(theCategoryCoding.getSystem(), theCategoryCoding.getCode())));
coding_codes.add(theCategoryCoding.getCode());
coding_displays.add(theCategoryCoding.getDisplay());
coding_systems.add(theCategoryCoding.getSystem());
}
myCategory_coding_code_system_hash.add(coding_code_system_hashes);
myCategory_coding_code.add(coding_codes);
myCategory_coding_display.add(coding_displays);
myCategory_coding_system.add(coding_systems);
}
}
public void setCategories(List<CodeableConcept> theCategories) {
for (CodeableConcept theConcept : theCategories) {
myCategory_concept_text.add(theConcept.getText());
List<String> coding_code_system_hashes = new ArrayList<>();
List<String> coding_codes = new ArrayList<>();
List<String> coding_displays = new ArrayList<>();
List<String> coding_systems = new ArrayList<>();
for (Coding theCategoryCoding : theConcept.getCoding()) {
coding_code_system_hashes.add(String.valueOf(CodeSystemHash.hashCodeSystem(theCategoryCoding.getSystem(), theCategoryCoding.getCode())));
coding_codes.add(theCategoryCoding.getCode());
coding_displays.add(theCategoryCoding.getDisplay());
coding_systems.add(theCategoryCoding.getSystem());
}
myCategory_coding_code_system_hash.add(coding_code_system_hashes);
myCategory_coding_code.add(coding_codes);
myCategory_coding_display.add(coding_displays);
myCategory_coding_system.add(coding_systems);
}
}
public List<String> getCategory_concept_text() {
return myCategory_concept_text;
}
public List<String> getCategory_concept_text() {
return myCategory_concept_text;
}
public List<List<String>> getCategory_coding_code_system_hash() {
return myCategory_coding_code_system_hash;
}
public List<List<String>> getCategory_coding_code_system_hash() {
return myCategory_coding_code_system_hash;
}
public List<List<String>> getCategory_coding_code() {
return myCategory_coding_code;
}
public List<List<String>> getCategory_coding_code() {
return myCategory_coding_code;
}
public List<List<String>> getCategory_coding_display() {
return myCategory_coding_display;
}
public List<List<String>> getCategory_coding_display() {
return myCategory_coding_display;
}
public List<List<String>> getCategory_coding_system() {
return myCategory_coding_system;
}
public List<List<String>> getCategory_coding_system() {
return myCategory_coding_system;
}
public void setCode(CodeableConcept theCode) {
myCode_concept_text = theCode.getText();
for(Coding theCodeCoding : theCode.getCoding()) {
// TODO: Temporary change until address how to manage multiple Observation Code codings.
/* myCode_coding_code_system_hash.add(String.valueOf(CodeSystemHash.hashCodeSystem(theCodeCoding.getSystem(), theCodeCoding.getCode())));
myCode_coding_code.add(theCodeCoding.getCode());
myCode_coding_display.add(theCodeCoding.getDisplay());
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 void setCode(CodeableConcept theCode) {
myCode_concept_text = theCode.getText();
for (Coding theCodeCoding : theCode.getCoding()) {
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() {
return myCode_concept_text;
}
public String getCode_concept_text() {
return myCode_concept_text;
}
// TODO: Temporary changes until resolve problem of how to manage Observation Code with multiple codings
/*
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_system_hash() {
return myCode_coding_code_system_hash;
}
public String getCode_coding_code() {
return myCode_coding_code;
@ -180,28 +149,28 @@ public class ObservationJson {
return myCode_coding_system;
}
public void setCode_concept_id(String theCodeId) {
myCode_concept_id = theCodeId;
}
public void setCode_concept_id(String theCodeId) {
myCode_concept_id = theCodeId;
}
public String getCode_concept_id() {
return myCode_concept_id;
}
public String getCode_concept_id() {
return myCode_concept_id;
}
public void setEffectiveDtm(Date theEffectiveDtm) {
myEffectiveDtm = theEffectiveDtm;
}
public void setEffectiveDtm(Date theEffectiveDtm) {
myEffectiveDtm = theEffectiveDtm;
}
public Date getEffectiveDtm() {
return myEffectiveDtm;
}
public Date getEffectiveDtm() {
return myEffectiveDtm;
}
public String getSubject() {
return mySubject;
}
public String getSubject() {
return mySubject;
}
public String getIdentifier() {
return myIdentifier;
}
public String getIdentifier() {
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);
myDaoConfig.setSearchPreFetchThresholds(mySmallerPreFetchThresholds);
SearchBuilder.setIsTest(true);
SearchBuilder.setMaxPageSize50ForTest(true);
}
@After
public void after() {
myDaoConfig.setSearchPreFetchThresholds(originalPreFetchThresholds);
SearchBuilder.setIsTest(false);
SearchBuilder.setMaxPageSize50ForTest(false);
}
@Test
@ -82,7 +82,7 @@ public class FhirResourceDaoR4SearchLastNAsyncIT extends BaseR4SearchLastN {
when(mySrd.getParameters()).thenReturn(requestParameters);
// Set chunk size to 50
SearchBuilder.setIsTest(true);
SearchBuilder.setMaxPageSize50ForTest(true);
// Expand default fetch sizes to ensure all observations are returned in first page:
List<Integer> myBiggerPreFetchThresholds = new ArrayList<>();

View File

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

View File

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

View File

@ -54,7 +54,7 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
@After
public void after() throws IOException {
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.CODE_INDEX);
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
}
@Test
@ -307,18 +307,12 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
String codeableConceptId1 = UUID.randomUUID().toString();
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"));
// 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);
String codeJson1Document = ourMapperNonPrettyPrint.writeValueAsString(codeJson1);
String codeableConceptId2 = UUID.randomUUID().toString();
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"));
// 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);
String codeJson2Document = ourMapperNonPrettyPrint.writeValueAsString(codeJson2);
@ -357,12 +351,12 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
observationJson.setCategories(categoryConcepts1);
observationJson.setCode(codeableConceptField1);
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 {
observationJson.setCategories(categoryConcepts2);
observationJson.setCode(codeableConceptField2);
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();

View File

@ -1,27 +1,37 @@
package ca.uhn.fhir.jpa.search.lastn;
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.json.CodeJson;
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.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.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.hl7.fhir.r4.model.CodeableConcept;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
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.assertTrue;
@ -68,12 +78,6 @@ public class LastNElasticsearchSvcSingleObservationIT {
final String CODEFIRSTCODINGSYSTEM = "http://mycodes.org/fhir/observation-code";
final String CODEFIRSTCODINGCODE = "test-code";
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();
@ -89,7 +93,7 @@ public class LastNElasticsearchSvcSingleObservationIT {
@After
public void after() throws IOException {
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.CODE_INDEX);
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
}
@Test
@ -209,32 +213,6 @@ public class LastNElasticsearchSvcSingleObservationIT {
String code_concept_text_values = observation.getCode_concept_text();
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();
assertEquals(CODEFIRSTCODINGSYSTEM, code_coding_systems);
@ -258,36 +236,20 @@ public class LastNElasticsearchSvcSingleObservationIT {
assertEquals(OBSERVATIONCODETEXT, persistedCodeConceptText);
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(CODEFIRSTCODINGSYSTEM, persistedCodeCodingSystems.get(0));
// assertEquals(CODESECONDCODINGSYSTEM, persistedCodeCodingSystems.get(1));
// assertEquals(CODETHIRDCODINGSYSTEM, persistedCodeCodingSystems.get(2));
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(CODEFIRSTCODINGCODE, persistedCodeCodingCodes.get(0));
// assertEquals(CODESECONDCODINGCODE, persistedCodeCodingCodes.get(1));
// assertEquals(CODETHIRDCODINGCODE, persistedCodeCodingCodes.get(2));
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(CODEFIRSTCODINGDISPLAY, persistedCodeCodingDisplays.get(0));
// assertEquals(CODESECONDCODINGDISPLAY, persistedCodeCodingDisplays.get(1));
// assertEquals(CODETHIRDCODINGDISPLAY, persistedCodeCodingDisplays.get(2));
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(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);
CodeableConcept codeableConceptField = new CodeableConcept().setText(OBSERVATIONCODETEXT);
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);
String observationDocument = ourMapperNonPrettyPrint.writeValueAsString(indexedObservation);
@ -340,7 +299,7 @@ public class LastNElasticsearchSvcSingleObservationIT {
CodeJson observationCode = new CodeJson(codeableConceptField, OBSERVATIONSINGLECODEID);
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 {
Thread.sleep(1000L);

View File

@ -47,9 +47,4 @@ public class TestElasticsearchConfig {
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)
private String myCodeableConceptText;
// TODO: Make coding a Collection. Need to first figure out how to maintain this over time.
@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"))
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
// private Set<ObservationIndexedCodeCodingEntity> myObservationIndexedCodeCodingEntitySet;
private ObservationIndexedCodeCodingEntity myObservationIndexedCodeCodingEntity;
public ObservationIndexedCodeCodeableConceptEntity() {
@ -42,10 +39,6 @@ public class ObservationIndexedCodeCodeableConceptEntity {
}
public void addCoding(ObservationIndexedCodeCodingEntity theObservationIndexedCodeCodingEntity) {
// if (myObservationIndexedCodeCodingEntitySet == null) {
// myObservationIndexedCodeCodingEntitySet = new HashSet<>();
// }
// myObservationIndexedCodeCodingEntitySet.add(theObservationIndexedCodeCodingEntity);
myObservationIndexedCodeCodingEntity = theObservationIndexedCodeCodingEntity;
}
@ -65,6 +58,4 @@ public class ObservationIndexedCodeCodeableConceptEntity {
myCodeableConceptText = theCodeableConceptText;
}
}

View File

@ -13,36 +13,31 @@ public class ObservationIndexedCodeCodingEntity {
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
@Column(name="CODEABLE_CONCEPT_ID", length = MAX_LENGTH)
private String myCodeableConceptId;
@Column(name = "CODEABLE_CONCEPT_ID", length = MAX_LENGTH)
private String myCodeableConceptId;
@Field (name = "code", analyze = Analyze.NO)
private String myCode;
@Field(name = "code", analyze = Analyze.NO)
private String myCode;
@Field (name = "system", analyze = Analyze.NO)
private String mySystem;
@Field(name = "system", analyze = Analyze.NO)
private String mySystem;
@Field (name = "code_system_hash", analyze = Analyze.NO)
private String myCodeSystemHash;
@Field(name = "code_system_hash", analyze = Analyze.NO)
private String myCodeSystemHash;
@Field (name = "display")
private String myDisplay;
@Field(name = "display")
private String myDisplay;
public ObservationIndexedCodeCodingEntity() {}
public ObservationIndexedCodeCodingEntity() {
}
public ObservationIndexedCodeCodingEntity(String theSystem, String theCode, String theDisplay, String theCodeableConceptId) {
myCode = theCode;
mySystem = theSystem;
myCodeSystemHash = String.valueOf(CodeSystemHash.hashCodeSystem(theSystem, theCode));
myDisplay = theDisplay;
myCodeableConceptId = theCodeableConceptId;
}
public ObservationIndexedCodeCodingEntity(String theSystem, String theCode, String theDisplay, String theCodeableConceptId) {
myCode = theCode;
mySystem = theSystem;
myCodeSystemHash = String.valueOf(CodeSystemHash.hashCodeSystem(theSystem, theCode));
myDisplay = theDisplay;
myCodeableConceptId = theCodeableConceptId;
}
}

View File

@ -10,32 +10,47 @@ public class LastNParameterHelper {
if (theParamName == null) {
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)
|| theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_CATEGORY) || theParamName.equals(org.hl7.fhir.r5.model.Observation.SP_CODE)) {
return true;
} else {
return false;
}
} else if (theContext.getVersion().getVersion() == FhirVersionEnum.R4) {
if (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)) {
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;
}
FhirVersionEnum version = theContext.getVersion().getVersion();
if (isR5(version) && isLastNParameterR5(theParamName)) {
return true;
} else if (isR4(version) && isLastNParameterR4(theParamName)) {
return true;
} else if (isDstu3(version) && isLastNParameterDstu3(theParamName)) {
return true;
} 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) {
if (theContext.getVersion().getVersion() == FhirVersionEnum.R5) {
return org.hl7.fhir.r5.model.Observation.SP_SUBJECT;