Merge remote-tracking branch 'remotes/origin/master' into im_2020601_lastn_code_text_filter

# Conflicts:
#	hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/lastn/LastNElasticsearchSvcMultipleObservationsIT.java
This commit is contained in:
ianmarshall 2020-06-04 16:55:18 -04:00
commit 78b7f091ec
50 changed files with 1099 additions and 1028 deletions

12
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# Basic dependabot.yml file with
# minimum configuration for two package managers
version: 2
updates:
# Enable version updates for Maven
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "daily"

View File

@ -2057,7 +2057,7 @@ public enum Pointcut {
return myParameterTypes;
}
private class UnknownType {
private static class UnknownType {
}
private static class ExceptionHandlingSpec {

View File

@ -43,6 +43,7 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
private static final FastDateFormat ourHumanDateTimeFormat = FastDateFormat.getDateTimeInstance(FastDateFormat.MEDIUM, FastDateFormat.MEDIUM);
private static final FastDateFormat ourXmlDateTimeFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
public static final String NOW_DATE_CONSTANT = "%now";
public static final String TODAY_DATE_CONSTANT = "%today";
private String myFractionalSeconds;
private TemporalPrecisionEnum myPrecision = null;
private TimeZone myTimeZone;
@ -638,6 +639,9 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
if (NOW_DATE_CONSTANT.equalsIgnoreCase(theValue)) {
super.setValueAsString(ourXmlDateTimeFormat.format(new Date()));
} else if (TODAY_DATE_CONSTANT.equalsIgnoreCase(theValue)) {
super.setValueAsString(ourXmlDateTimeFormat.format(new Date()));
setPrecision(TemporalPrecisionEnum.DAY);
} else {
super.setValueAsString(theValue);
}

View File

@ -0,0 +1,7 @@
---
type: add
issue: 1838
title: "Date searches can be performed relative to 'now' using the `%now` parameter value. For example, to search for Procedures with a date later than now, you can search for `/Procedure?date=ge%now`.
Note the '%' will need to be URL escaped so the actual URL will be `/Procedure?date=ge%25now`.
The way this works is the server substitutes `%now` with the current date and time in the standard FHIR format `yyyy-MM-ddTHH:mm:ss` before submitting it to the FHIR Storage module.
Similarly date searches can be performed relative to 'today' using the '%today' parameter value. '%today' works the same as '%now' except that it searches as a 'date' type as opposed to a 'dateTime' type."

View File

@ -243,7 +243,7 @@ The operation returns the updated `Person` resource. Note that this is the only
## Merge Persons
The `$empi-merge-persons` operation can be used to merge one Person resource with another. When doing this, you will need to decide which resource to delete and which one to keep. Data from the personToKeep will be given precedence over data in the personToDelete. This operation takes the following parameters:
The `$empi-merge-persons` operation can be used to merge one Person resource with another. When doing this, you will need to decide which resource to merge from and which one to merge to. In most cases, fields will be merged (e.g. names, identifiers, and links will be the union of two). However when there is a conflict (e.g. birthday), fields in the toPerson will take precedence over fields in the fromPerson. This operation takes the following parameters:
<table class="table table-striped table-condensed">
<thead>
@ -256,15 +256,15 @@ The `$empi-merge-persons` operation can be used to merge one Person resource wit
</thead>
<tbody>
<tr>
<td>personIdToDelete</td>
<td>fromPersonId</td>
<td>String</td>
<td>1..1</td>
<td>
The id of the Person resource to merge data from. This resource will be deleted after the merge.
The id of the Person resource to merge data from. "active" will be set to "false" on this resource after the merge.
</td>
</tr>
<tr>
<td>personIdToKeep</td>
<td>toPersonId</td>
<td>String</td>
<td>1..1</td>
<td>
@ -288,10 +288,10 @@ The following request body could be used:
{
"resourceType": "Parameters",
"parameter": [ {
"name": "personIdToDelete",
"name": "fromPersonId",
"valueString": "Person/123"
}, {
"name": "personIdToKeep",
"name": "toPersonId",
"valueString": "Patient/128"
} ]
}

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.jpa.dao.data.IEmpiLinkDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.EmpiLink;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Patient;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
@ -172,7 +173,7 @@ public class EmpiLinkDaoSvc {
}
public List<EmpiLink> findEmpiLinksByPersonId(IBaseResource thePersonResource) {
@Nullable Long pid = myIdHelperService.getPidOrNull(thePersonResource);
Long pid = myIdHelperService.getPidOrNull(thePersonResource);
if (pid == null) {
return Collections.emptyList();
}
@ -192,4 +193,14 @@ public class EmpiLinkDaoSvc {
public List<EmpiLink> findEmpiLinkByExample(Example<EmpiLink> theExampleLink) {
return myEmpiLinkDao.findAll(theExampleLink);
}
public List<EmpiLink> findEmpiLinksByTarget(Patient theTargetResource) {
Long pid = myIdHelperService.getPidOrNull(theTargetResource);
if (pid == null) {
return Collections.emptyList();
}
EmpiLink empiLink = new EmpiLink().setTargetPid(pid);
Example<EmpiLink> example = Example.of(empiLink);
return myEmpiLinkDao.findAll(example);
}
}

View File

@ -21,44 +21,34 @@ package ca.uhn.fhir.jpa.dao;
*/
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedCodeCodingSearchParamDao;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedSearchParamLastNDao;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCategoryCodeableConceptEntity;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCategoryCodingEntity;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodingEntity;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import ca.uhn.fhir.jpa.search.lastn.IElasticsearchSvc;
import ca.uhn.fhir.jpa.search.lastn.json.CodeJson;
import ca.uhn.fhir.jpa.search.lastn.json.ObservationJson;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef;
import org.hl7.fhir.instance.model.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import java.util.*;
@Transactional(propagation = Propagation.REQUIRED)
public class ObservationLastNIndexPersistSvc {
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
@Autowired
IObservationIndexedSearchParamLastNDao myResourceIndexedObservationLastNDao;
private ISearchParamExtractor mySearchParameterExtractor;
@Autowired
IObservationIndexedCodeCodingSearchParamDao myObservationIndexedCodeCodingSearchParamDao;
@Autowired
public ISearchParamExtractor mySearchParameterExtractor;
@Autowired(required = false)
private IElasticsearchSvc myElasticsearchSvc;
public void indexObservation(IBaseResource theResource) {
if (myElasticsearchSvc == null) {
// Elasticsearch is not enabled and therefore no index needs to be updated.
return;
}
List<IBase> subjectReferenceElement = mySearchParameterExtractor.extractValues("Observation.subject", theResource);
String subjectId = subjectReferenceElement.stream()
.map(refElement -> mySearchParameterExtractor.extractReferenceLinkFromResource(refElement, "Observation.subject"))
@ -95,15 +85,12 @@ public class ObservationLastNIndexPersistSvc {
List<IBase> theObservationCategoryCodeableConcepts) {
// Determine if an index already exists for Observation:
boolean observationIndexUpdate = false;
ObservationIndexedSearchParamLastNEntity indexedObservation = null;
ObservationJson indexedObservation = null;
if (resourcePID != null) {
indexedObservation = myResourceIndexedObservationLastNDao.findByIdentifier(resourcePID);
indexedObservation = myElasticsearchSvc.getObservationDocument(resourcePID);
}
if (indexedObservation == null) {
indexedObservation = new ObservationIndexedSearchParamLastNEntity();
} else {
observationIndexUpdate = true;
indexedObservation = new ObservationJson();
}
indexedObservation.setEffectiveDtm(theEffectiveDtm);
@ -114,77 +101,65 @@ public class ObservationLastNIndexPersistSvc {
addCategoriesToObservationIndex(theObservationCategoryCodeableConcepts, indexedObservation);
if (observationIndexUpdate) {
myEntityManager.merge(indexedObservation);
} else {
myEntityManager.persist(indexedObservation);
}
myElasticsearchSvc.createOrUpdateObservationIndex(resourcePID, indexedObservation);
}
private void addCodeToObservationIndex(List<IBase> theObservationCodeCodeableConcepts,
ObservationIndexedSearchParamLastNEntity theIndexedObservation) {
ObservationJson theIndexedObservation) {
// Determine if a Normalized ID was created previously for Observation Code
Optional<String> existingObservationCodeNormalizedId = getCodeCodeableConceptIdIfExists(theObservationCodeCodeableConcepts.get(0));
String existingObservationCodeNormalizedId = getCodeCodeableConceptId(theObservationCodeCodeableConcepts.get(0));
// Create/update normalized Observation Code index record
ObservationIndexedCodeCodeableConceptEntity codeableConceptField =
CodeJson codeableConceptField =
getCodeCodeableConcept(theObservationCodeCodeableConcepts.get(0),
existingObservationCodeNormalizedId.orElse(UUID.randomUUID().toString()));
existingObservationCodeNormalizedId);
if (existingObservationCodeNormalizedId.isPresent()) {
myEntityManager.merge(codeableConceptField);
} else {
myEntityManager.persist(codeableConceptField);
}
theIndexedObservation.setObservationCode(codeableConceptField);
theIndexedObservation.setCodeNormalizedId(codeableConceptField.getCodeableConceptId());
myElasticsearchSvc.createOrUpdateObservationCodeIndex(codeableConceptField.getCodeableConceptId(), codeableConceptField);
theIndexedObservation.setCode(codeableConceptField);
theIndexedObservation.setCode_concept_id(codeableConceptField.getCodeableConceptId());
}
private void addCategoriesToObservationIndex(List<IBase> observationCategoryCodeableConcepts,
ObservationIndexedSearchParamLastNEntity indexedObservation) {
ObservationJson indexedObservation) {
// Build CodeableConcept entities for Observation.Category
Set<ObservationIndexedCategoryCodeableConceptEntity> categoryCodeableConceptEntities = new HashSet<>();
List<CodeJson> categoryCodeableConceptEntities = new ArrayList<>();
for (IBase categoryCodeableConcept : observationCategoryCodeableConcepts) {
// Build CodeableConcept entities for each category CodeableConcept
categoryCodeableConceptEntities.add(getCategoryCodeableConceptEntities(categoryCodeableConcept));
}
indexedObservation.setCategoryCodeableConcepts(categoryCodeableConceptEntities);
indexedObservation.setCategories(categoryCodeableConceptEntities);
}
private ObservationIndexedCategoryCodeableConceptEntity getCategoryCodeableConceptEntities(IBase theValue) {
private CodeJson getCategoryCodeableConceptEntities(IBase theValue) {
String text = mySearchParameterExtractor.getDisplayTextFromCodeableConcept(theValue);
ObservationIndexedCategoryCodeableConceptEntity categoryCodeableConcept = new ObservationIndexedCategoryCodeableConceptEntity(text);
CodeJson categoryCodeableConcept = new CodeJson();
categoryCodeableConcept.setCodeableConceptText(text);
List<IBase> codings = mySearchParameterExtractor.getCodingsFromCodeableConcept(theValue);
Set<ObservationIndexedCategoryCodingEntity> categoryCodingEntities = new HashSet<>();
for (IBase nextCoding : codings) {
categoryCodingEntities.add(getCategoryCoding(nextCoding));
addCategoryCoding(nextCoding, categoryCodeableConcept);
}
categoryCodeableConcept.setObservationIndexedCategoryCodingEntitySet(categoryCodingEntities);
return categoryCodeableConcept;
}
private ObservationIndexedCodeCodeableConceptEntity getCodeCodeableConcept(IBase theValue, String observationCodeNormalizedId) {
private CodeJson getCodeCodeableConcept(IBase theValue, String observationCodeNormalizedId) {
String text = mySearchParameterExtractor.getDisplayTextFromCodeableConcept(theValue);
ObservationIndexedCodeCodeableConceptEntity codeCodeableConcept = new ObservationIndexedCodeCodeableConceptEntity(text, observationCodeNormalizedId);
CodeJson codeCodeableConcept = new CodeJson();
codeCodeableConcept.setCodeableConceptText(text);
codeCodeableConcept.setCodeableConceptId(observationCodeNormalizedId);
List<IBase> codings = mySearchParameterExtractor.getCodingsFromCodeableConcept(theValue);
for (IBase nextCoding : codings) {
codeCodeableConcept.addCoding(getCodeCoding(nextCoding, observationCodeNormalizedId));
addCodeCoding(nextCoding, codeCodeableConcept);
}
return codeCodeableConcept;
}
private Optional<String> getCodeCodeableConceptIdIfExists(IBase theValue) {
private String getCodeCodeableConceptId(IBase theValue) {
List<IBase> codings = mySearchParameterExtractor.getCodingsFromCodeableConcept(theValue);
String codeCodeableConceptId = null;
Optional<String> codeCodeableConceptIdOptional = Optional.empty();
for (IBase nextCoding : codings) {
@ -196,52 +171,52 @@ public class ObservationLastNIndexPersistSvc {
String system = param.getSystem();
String code = param.getValue();
String text = mySearchParameterExtractor.getDisplayTextForCoding(nextCoding);
if (code != null && system != null) {
codeCodeableConceptIdOptional = Optional.ofNullable(myObservationIndexedCodeCodingSearchParamDao.findByCodeAndSystem(code, system));
} else {
codeCodeableConceptIdOptional = Optional.ofNullable(myObservationIndexedCodeCodingSearchParamDao.findByDisplay(text));
}
if (codeCodeableConceptIdOptional.isPresent()) {
String codeSystemHash = String.valueOf(CodeSystemHash.hashCodeSystem(system, code));
CodeJson codeCodeableConceptDocument = myElasticsearchSvc.getObservationCodeDocument(codeSystemHash, text);
if (codeCodeableConceptDocument != null) {
codeCodeableConceptIdOptional = Optional.of(codeCodeableConceptDocument.getCodeableConceptId());
break;
}
}
}
return codeCodeableConceptIdOptional;
return codeCodeableConceptIdOptional.orElse(UUID.randomUUID().toString());
}
private ObservationIndexedCategoryCodingEntity getCategoryCoding(IBase theValue) {
private void addCategoryCoding(IBase theValue, CodeJson theCategoryCodeableConcept) {
ResourceIndexedSearchParamToken param = mySearchParameterExtractor.createSearchParamForCoding("Observation",
new RuntimeSearchParam(null, null, "category", null, null, null, null, null, null, null),
theValue);
ObservationIndexedCategoryCodingEntity observationIndexedCategoryCodingEntity = null;
if (param != null) {
String system = param.getSystem();
String code = param.getValue();
String text = mySearchParameterExtractor.getDisplayTextForCoding(theValue);
observationIndexedCategoryCodingEntity = new ObservationIndexedCategoryCodingEntity(system, code, text);
theCategoryCodeableConcept.addCoding(system, code, text);
}
return observationIndexedCategoryCodingEntity;
}
private ObservationIndexedCodeCodingEntity getCodeCoding(IBase theValue, String observationCodeNormalizedId) {
private void addCodeCoding(IBase theValue, CodeJson theObservationCode) {
ResourceIndexedSearchParamToken param = mySearchParameterExtractor.createSearchParamForCoding("Observation",
new RuntimeSearchParam(null, null, "code", null, null, null, null, null, null, null),
theValue);
ObservationIndexedCodeCodingEntity observationIndexedCodeCodingEntity = null;
if (param != null) {
String system = param.getSystem();
String code = param.getValue();
String text = mySearchParameterExtractor.getDisplayTextForCoding(theValue);
observationIndexedCodeCodingEntity = new ObservationIndexedCodeCodingEntity(system, code, text, observationCodeNormalizedId);
theObservationCode.addCoding(system, code, text);
}
return observationIndexedCodeCodingEntity;
}
public void deleteObservationIndex(IBasePersistedResource theEntity) {
ObservationIndexedSearchParamLastNEntity deletedObservationLastNEntity = myResourceIndexedObservationLastNDao.findByIdentifier(theEntity.getIdDt().getIdPart());
if (myElasticsearchSvc == null) {
// Elasticsearch is not enabled and therefore no index needs to be updated.
return;
}
ObservationJson deletedObservationLastNEntity = myElasticsearchSvc.getObservationDocument(theEntity.getIdDt().getIdPart());
if (deletedObservationLastNEntity != null) {
myEntityManager.remove(deletedObservationLastNEntity);
myElasticsearchSvc.deleteObservationDocument(deletedObservationLastNEntity.getIdentifier());
}
}

View File

@ -1,37 +0,0 @@
package ca.uhn.fhir.jpa.dao.data;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface IObservationIndexedCodeCodeableConceptSearchParamDao extends JpaRepository<ObservationIndexedCodeCodeableConceptEntity, Long> {
@Query("" +
"SELECT t FROM ObservationIndexedCodeCodeableConceptEntity t " +
"WHERE t.myCodeableConceptId = :codeableConceptId" +
"")
ObservationIndexedCodeCodeableConceptEntity findByCodeableConceptId(@Param("codeableConceptId") String theCodeableConceptId);
}

View File

@ -1,46 +0,0 @@
package ca.uhn.fhir.jpa.dao.data;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodingEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface IObservationIndexedCodeCodingSearchParamDao extends JpaRepository<ObservationIndexedCodeCodingEntity, Long> {
@Query("" +
"SELECT t.myCodeableConceptId FROM ObservationIndexedCodeCodingEntity t " +
"WHERE t.myCode = :code " +
"AND t.mySystem = :system " +
"")
String findByCodeAndSystem(@Param("code") String theCode, @Param("system") String theSystem);
@Query("" +
"SELECT t.myCodeableConceptId FROM ObservationIndexedCodeCodingEntity t " +
"WHERE t.myDisplay = :display" +
"")
String findByDisplay(@Param("display") String theDisplay);
}

View File

@ -1,37 +0,0 @@
package ca.uhn.fhir.jpa.dao.data;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface IObservationIndexedSearchParamLastNDao extends JpaRepository<ObservationIndexedSearchParamLastNEntity, Long>{
@Query("" +
"SELECT t FROM ObservationIndexedSearchParamLastNEntity t " +
"WHERE t.myIdentifier = :identifier" +
"")
ObservationIndexedSearchParamLastNEntity findByIdentifier(@Param("identifier") String theIdentifier);
}

View File

@ -39,6 +39,7 @@ 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.admin.indices.refresh.RefreshRequest;
import org.shadehapi.elasticsearch.action.index.IndexRequest;
import org.shadehapi.elasticsearch.action.index.IndexResponse;
import org.shadehapi.elasticsearch.action.search.SearchRequest;
@ -232,7 +233,7 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
private CompositeAggregationBuilder createObservationSubjectAggregationBuilder(int theMaxNumberObservationsPerCode, String[] theTopHitsInclude) {
CompositeValuesSourceBuilder<?> subjectValuesBuilder = new TermsValuesSourceBuilder("subject").field("subject");
List<CompositeValuesSourceBuilder<?>> compositeAggSubjectSources = new ArrayList();
List<CompositeValuesSourceBuilder<?>> compositeAggSubjectSources = new ArrayList<>();
compositeAggSubjectSources.add(subjectValuesBuilder);
CompositeAggregationBuilder compositeAggregationSubjectBuilder = new CompositeAggregationBuilder(GROUP_BY_SUBJECT, compositeAggSubjectSources);
compositeAggregationSubjectBuilder.subAggregation(createObservationCodeAggregationBuilder(theMaxNumberObservationsPerCode, theTopHitsInclude));
@ -554,12 +555,12 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
}
@VisibleForTesting
List<ObservationJson> executeLastNWithAllFields(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext) {
public List<ObservationJson> executeLastNWithAllFieldsForTest(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext) {
return buildAndExecuteSearch(theSearchParameterMap, theFhirContext, null, t -> t, 100);
}
@VisibleForTesting
List<CodeJson> queryAllIndexedObservationCodes() throws IOException {
List<CodeJson> queryAllIndexedObservationCodesForTest() throws IOException {
SearchRequest codeSearchRequest = new SearchRequest(OBSERVATION_CODE_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// Query
@ -580,8 +581,104 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
return codes;
}
@VisibleForTesting
boolean performIndex(String theIndexName, String theDocumentId, String theIndexDocument, String theDocumentType) throws IOException {
@Override
public ObservationJson getObservationDocument(String theDocumentID) {
if (theDocumentID == null) {
throw new InvalidRequestException("Require non-null document ID for observation document query");
}
SearchRequest theSearchRequest = buildSingleObservationSearchRequest(theDocumentID);
ObservationJson observationDocumentJson = null;
try {
SearchResponse observationDocumentResponse = executeSearchRequest(theSearchRequest);
SearchHit[] observationDocumentHits = observationDocumentResponse.getHits().getHits();
if (observationDocumentHits.length > 0) {
// There should be no more than one hit for the identifier
String observationDocument = observationDocumentHits[0].getSourceAsString();
observationDocumentJson = objectMapper.readValue(observationDocument, ObservationJson.class);
}
} catch (IOException theE) {
throw new InvalidRequestException("Unable to execute observation document query for ID " + theDocumentID, theE);
}
return observationDocumentJson;
}
private SearchRequest buildSingleObservationSearchRequest(String theObservationIdentifier) {
SearchRequest searchRequest = new SearchRequest(OBSERVATION_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.termQuery(OBSERVATION_IDENTIFIER_FIELD_NAME, theObservationIdentifier));
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.size(1);
searchRequest.source(searchSourceBuilder);
return searchRequest;
}
@Override
public CodeJson getObservationCodeDocument(String theCodeSystemHash, String theText) {
if(theCodeSystemHash == null && theText == null) {
throw new InvalidRequestException("Require a non-null code system hash value or display value for observation code document query");
}
SearchRequest theSearchRequest = buildSingleObservationCodeSearchRequest(theCodeSystemHash, theText);
CodeJson observationCodeDocumentJson = null;
try {
SearchResponse observationCodeDocumentResponse = executeSearchRequest(theSearchRequest);
SearchHit[] observationCodeDocumentHits = observationCodeDocumentResponse.getHits().getHits();
if (observationCodeDocumentHits.length > 0) {
// There should be no more than one hit for the code lookup.
String observationCodeDocument = observationCodeDocumentHits[0].getSourceAsString();
observationCodeDocumentJson = objectMapper.readValue(observationCodeDocument, CodeJson.class);
}
} catch (IOException theE) {
throw new InvalidRequestException("Unable to execute observation code document query hash code or display", theE);
}
return observationCodeDocumentJson;
}
private SearchRequest buildSingleObservationCodeSearchRequest(String theCodeSystemHash, String theText) {
SearchRequest searchRequest = new SearchRequest(OBSERVATION_CODE_INDEX);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
if (theCodeSystemHash != null) {
boolQueryBuilder.must(QueryBuilders.termQuery("codingcode_system_hash", theCodeSystemHash));
} else {
boolQueryBuilder.must(QueryBuilders.matchPhraseQuery("text", theText));
}
searchSourceBuilder.query(boolQueryBuilder);
searchSourceBuilder.size(1);
searchRequest.source(searchSourceBuilder);
return searchRequest;
}
@Override
public Boolean createOrUpdateObservationIndex(String theDocumentId, ObservationJson theObservationDocument){
try {
String documentToIndex = objectMapper.writeValueAsString(theObservationDocument);
return performIndex(OBSERVATION_INDEX, theDocumentId, documentToIndex, ElasticsearchSvcImpl.OBSERVATION_DOCUMENT_TYPE);
} catch (IOException theE) {
throw new InvalidRequestException("Unable to persist Observation document " + theDocumentId);
}
}
@Override
public Boolean createOrUpdateObservationCodeIndex(String theCodeableConceptID, CodeJson theObservationCodeDocument) {
try {
String documentToIndex = objectMapper.writeValueAsString(theObservationCodeDocument);
return performIndex(OBSERVATION_CODE_INDEX, theCodeableConceptID, documentToIndex, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE);
} catch (IOException theE) {
throw new InvalidRequestException("Unable to persist Observation Code document " + theCodeableConceptID);
}
}
private boolean performIndex(String theIndexName, String theDocumentId, String theIndexDocument, String theDocumentType) throws IOException {
IndexResponse indexResponse = myRestHighLevelClient.index(createIndexRequest(theIndexName, theDocumentId, theIndexDocument, theDocumentType),
RequestOptions.DEFAULT);
@ -597,11 +694,27 @@ public class ElasticsearchSvcImpl implements IElasticsearchSvc {
return request;
}
@Override
public void deleteObservationDocument(String theDocumentId) {
DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(OBSERVATION_INDEX);
deleteByQueryRequest.setQuery(QueryBuilders.termQuery(OBSERVATION_IDENTIFIER_FIELD_NAME, theDocumentId));
try {
myRestHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
} catch (IOException theE) {
throw new InvalidRequestException("Unable to delete Observation " + theDocumentId);
}
}
@VisibleForTesting
void deleteAllDocuments(String theIndexName) throws IOException {
public void deleteAllDocumentsForTest(String theIndexName) throws IOException {
DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(theIndexName);
deleteByQueryRequest.setQuery(QueryBuilders.matchAllQuery());
myRestHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
}
@VisibleForTesting
public void refreshIndex(String theIndexName) throws IOException {
myRestHighLevelClient.indices().refresh(new RefreshRequest(theIndexName), RequestOptions.DEFAULT);
}
}

View File

@ -21,6 +21,8 @@ package ca.uhn.fhir.jpa.search.lastn;
*/
import ca.uhn.fhir.context.FhirContext;
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 java.util.List;
@ -35,4 +37,42 @@ public interface IElasticsearchSvc {
* @return
*/
List<String> executeLastN(SearchParameterMap theSearchParameterMap, FhirContext theFhirContext, Integer theMaxResultsToFetch);
/**
* Returns index document for a single Observation
* @param theDocumentID Identifier of Observation resource.
* @return
*/
ObservationJson getObservationDocument(String theDocumentID);
/**
* Returns index document for a single Observation Code that either has a coding that matches a specified Code value and system or that has a specified text value.
* @param theCodeSystemHash A hash string constructed from a Code value and Code system used to match to an Observation Code.
* @param theText A text value used to match to an Observation Code.
* @return
*/
CodeJson getObservationCodeDocument(String theCodeSystemHash, String theText);
/**
* Creates or updates index for an Observation Resource.
* @param theDocumentId Identifier for Observation resource.
* @param theObservationDocument Indexing document for Observation.
* @return True if Observation indexed successfully.
*/
Boolean createOrUpdateObservationIndex(String theDocumentId, ObservationJson theObservationDocument);
/**
* Creates or updates index for an Observation Code.
* @param theCodeableConceptID Identifier for Observation resource.
* @param theObservationCodeDocument Indexing document for Observation.
* @return True if Observation Code indexed successfully.
*/
Boolean createOrUpdateObservationCodeIndex(String theCodeableConceptID, CodeJson theObservationCodeDocument);
/**
* Deletes index for an Observation Resource.
* @param theDocumentId Identifier for Observation resource.
*/
void deleteObservationDocument(String theDocumentId);
}

View File

@ -24,8 +24,6 @@ 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;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import java.util.ArrayList;
import java.util.List;
@ -55,22 +53,26 @@ public class CodeJson {
public CodeJson(){
}
public CodeJson(CodeableConcept theCodeableConcept, String theCodeableConceptId) {
myCodeableConceptText = theCodeableConcept.getText();
myCodeableConceptId = theCodeableConceptId;
for (Coding theCoding : theCodeableConcept.getCoding()) {
myCoding_code.add(theCoding.getCode());
myCoding_system.add(theCoding.getSystem());
myCoding_display.add(theCoding.getDisplay());
myCoding_code_system_hash.add(String.valueOf(CodeSystemHash.hashCodeSystem(theCoding.getSystem(), theCoding.getCode())));
}
}
public void setCodeableConceptId(String theCodeableConceptId) {
myCodeableConceptId = theCodeableConceptId;
}
public String getCodeableConceptId() {
public void addCoding(String theCoding_system, String theCoding_code, String theCoding_display) {
myCoding_code.add(theCoding_code);
myCoding_system.add(theCoding_system);
myCoding_display.add(theCoding_display);
myCoding_code_system_hash.add(String.valueOf(CodeSystemHash.hashCodeSystem(theCoding_system, theCoding_code)));
}
public String getCodeableConceptId() {
return myCodeableConceptId;
}
public String getCodeableConceptText() {
public void setCodeableConceptText(String theCodeableConceptText) {
myCodeableConceptText = theCodeableConceptText;
}
public String getCodeableConceptText() {
return myCodeableConceptText;
}
@ -78,11 +80,11 @@ public class CodeJson {
return myCoding_code;
}
public List<String> getCoding_code_system_hash() {
public List<String> getCoding_code_system_hash() {
return myCoding_code_system_hash;
}
public List<String> getCoding_display() {
public List<String> getCoding_display() {
return myCoding_display;
}

View File

@ -24,8 +24,6 @@ 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;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import java.util.ArrayList;
import java.util.Date;
@ -88,18 +86,24 @@ public class ObservationJson {
mySubject = theSubject;
}
public void setCategories(List<CodeableConcept> theCategories) {
for (CodeableConcept theConcept : theCategories) {
myCategory_concept_text.add(theConcept.getText());
public void setCategories(List<CodeJson> theCategories) {
for (CodeJson theConcept : theCategories) {
myCategory_concept_text.add(theConcept.getCodeableConceptText());
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());
for (String theCategoryCoding_code : theConcept.getCoding_code()) {
coding_codes.add(theCategoryCoding_code);
}
for (String theCategoryCoding_system : theConcept.getCoding_system()) {
coding_systems.add(theCategoryCoding_system);
}
for (String theCategoryCoding_code_system_hash : theConcept.getCoding_code_system_hash()) {
coding_code_system_hashes.add(theCategoryCoding_code_system_hash);
}
for (String theCategoryCoding_display : theConcept.getCoding_display()) {
coding_displays.add(theCategoryCoding_display);
}
myCategory_coding_code_system_hash.add(coding_code_system_hashes);
myCategory_coding_code.add(coding_codes);
@ -128,14 +132,13 @@ public class ObservationJson {
return myCategory_coding_system;
}
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 void setCode(CodeJson theCode) {
myCode_concept_text = theCode.getCodeableConceptText();
// Currently can only support one Coding for Observation Code
myCode_coding_code_system_hash = theCode.getCoding_code_system_hash().get(0);
myCode_coding_code = theCode.getCoding_code().get(0);
myCode_coding_display = theCode.getCoding_display().get(0);
myCode_coding_system = theCode.getCoding_system().get(0);
}

View File

@ -6,7 +6,7 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient;
import ca.uhn.fhir.jpa.config.TestR4ConfigWithElasticsearchClient;
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.param.DateAndListParam;
import ca.uhn.fhir.rest.param.DateOrListParam;
@ -18,7 +18,6 @@ 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 ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType;
@ -26,7 +25,6 @@ import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
@ -37,6 +35,7 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@ -44,11 +43,8 @@ import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
@ -70,6 +66,9 @@ public class BaseR4SearchLastN extends BaseJpaTest {
@Autowired
protected FhirContext myFhirCtx;
@Autowired
private ElasticsearchSvcImpl myElasticsearchSvc;
@Autowired
protected PlatformTransactionManager myPlatformTransactionManager;
@ -112,7 +111,7 @@ public class BaseR4SearchLastN extends BaseJpaTest {
private static Calendar observationDate = new GregorianCalendar();
@Before
public void beforeCreateTestPatientsAndObservations() {
public void beforeCreateTestPatientsAndObservations() throws IOException {
// Using a static flag to ensure that test data and elasticsearch index is only created once.
// Creating this data and the index is time consuming and as such want to avoid having to repeat for each test.
// Normally would use a static @BeforeClass method for this purpose, but Autowired objects cannot be accessed in static methods.
@ -131,6 +130,9 @@ public class BaseR4SearchLastN extends BaseJpaTest {
createObservationsForPatient(patient2Id);
dataLoaded = true;
myElasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
myElasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
}
}

View File

@ -48,6 +48,7 @@ import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
@ -641,6 +642,49 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
}
@Test
public void testCreateInTransaction_ServerId_WithPartition() {
createUniqueCompositeSp();
createRequestId();
addCreatePartition(myPartitionId, myPartitionDate);
addCreatePartition(myPartitionId, myPartitionDate);
Bundle input = new Bundle();
input.setType(Bundle.BundleType.TRANSACTION);
Organization org = new Organization();
org.setId(IdType.newRandomUuid());
org.setName("org");
input.addEntry()
.setFullUrl(org.getId())
.setResource(org)
.getRequest().setUrl("Organization").setMethod(Bundle.HTTPVerb.POST);
Patient p = new Patient();
p.getMeta().addTag("http://system", "code", "diisplay");
p.addName().setFamily("FAM");
p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date());
p.getManagingOrganization().setReference(org.getId());
input.addEntry()
.setFullUrl(p.getId())
.setResource(p)
.getRequest().setUrl("Patient").setMethod(Bundle.HTTPVerb.POST);
Bundle output = mySystemDao.transaction(mySrd, input);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
Long patientId = new IdType(output.getEntry().get(1).getResponse().getLocation()).getIdPartAsLong();
runInTransaction(() -> {
// HFJ_RESOURCE
ResourceTable resourceTable = myResourceTableDao.findById(patientId).orElseThrow(IllegalArgumentException::new);
assertEquals(myPartitionId, resourceTable.getPartitionId().getPartitionId().intValue());
assertEquals(myPartitionDate, resourceTable.getPartitionId().getPartitionDate());
});
}
@Test
public void testUpdateResourceWithPartition() {
createRequestId();

View File

@ -3,13 +3,12 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.TestR4ConfigWithElasticsearchClient;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedCodeCodeableConceptSearchParamDao;
import ca.uhn.fhir.jpa.dao.ObservationLastNIndexPersistSvc;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedCodeCodeableConceptEntity;
import ca.uhn.fhir.jpa.model.entity.ObservationIndexedSearchParamLastNEntity;
import ca.uhn.fhir.jpa.dao.data.IObservationIndexedSearchParamLastNDao;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
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.parser.IParser;
import ca.uhn.fhir.rest.param.*;
@ -42,12 +41,6 @@ import static org.junit.Assert.assertTrue;
@ContextConfiguration(classes = {TestR4ConfigWithElasticsearchClient.class})
public class PersistObservationIndexedSearchParamLastNR4IT {
@Autowired
IObservationIndexedSearchParamLastNDao myResourceIndexedObservationLastNDao;
@Autowired
IObservationIndexedCodeCodeableConceptSearchParamDao myCodeableConceptIndexedSearchParamNormalizedDao;
@Autowired
private ElasticsearchSvcImpl elasticsearchSvc;
@ -64,11 +57,12 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
protected FhirContext myFhirCtx;
@Before
public void before() {
myResourceIndexedObservationLastNDao.deleteAll();
myCodeableConceptIndexedSearchParamNormalizedDao.deleteAll();
public void before() throws IOException {
elasticsearchSvc.deleteAllDocumentsForTest(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocumentsForTest(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
}
private final String SINGLE_SUBJECT_ID = "4567";
@ -85,24 +79,34 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
private ReferenceAndListParam multiSubjectParams = null;
@Test
public void testIndexObservationSingle() {
public void testIndexObservationSingle() throws IOException {
indexSingleObservation();
List<ObservationIndexedSearchParamLastNEntity> persistedObservationEntities = myResourceIndexedObservationLastNDao.findAll();
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.setLastNMax(10);
List<ObservationJson> persistedObservationEntities = elasticsearchSvc.executeLastNWithAllFieldsForTest(searchParameterMap, myFhirCtx);
assertEquals(1, persistedObservationEntities.size());
ObservationIndexedSearchParamLastNEntity persistedObservationEntity = persistedObservationEntities.get(0);
ObservationJson persistedObservationEntity = persistedObservationEntities.get(0);
assertEquals(SINGLE_SUBJECT_ID, persistedObservationEntity.getSubject());
assertEquals(SINGLE_OBSERVATION_PID, persistedObservationEntity.getIdentifier());
assertEquals(SINGLE_EFFECTIVEDTM, persistedObservationEntity.getEffectiveDtm());
String observationCodeNormalizedId = persistedObservationEntity.getCodeNormalizedId();
String observationCodeNormalizedId = persistedObservationEntity.getCode_concept_id();
List<ObservationIndexedCodeCodeableConceptEntity> persistedObservationCodes = myCodeableConceptIndexedSearchParamNormalizedDao.findAll();
assertEquals(1, persistedObservationCodes.size());
ObservationIndexedCodeCodeableConceptEntity persistedObservationCode = persistedObservationCodes.get(0);
// List<CodeJson> persistedObservationCodes = elasticsearchSvc.queryAllIndexedObservationCodesForTest();
// assertEquals(1, persistedObservationCodes.size());
// Check that we can retrieve code by hash value.
String codeSystemHash = persistedObservationEntity.getCode_coding_code_system_hash();
CodeJson persistedObservationCode = elasticsearchSvc.getObservationCodeDocument(codeSystemHash, null);
assertNotNull(persistedObservationCode);
assertEquals(observationCodeNormalizedId, persistedObservationCode.getCodeableConceptId());
assertEquals(SINGLE_OBSERVATION_CODE_TEXT, persistedObservationCode.getCodeableConceptText());
SearchParameterMap searchParameterMap = new SearchParameterMap();
// Also confirm that we can retrieve code by text value.
persistedObservationCode = elasticsearchSvc.getObservationCodeDocument(null, SINGLE_OBSERVATION_CODE_TEXT);
assertNotNull(persistedObservationCode);
searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", SINGLE_SUBJECT_ID);
searchParameterMap.add(Observation.SP_SUBJECT, new ReferenceAndListParam().addAnd(new ReferenceOrListParam().addOr(subjectParam)));
TokenParam categoryParam = new TokenParam(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE);
@ -117,7 +121,7 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
assertEquals(SINGLE_OBSERVATION_PID, observationIdsOnly.get(0));
}
private void indexSingleObservation() {
private void indexSingleObservation() throws IOException {
Observation myObservation = new Observation();
IdType observationID = new IdType("Observation", SINGLE_OBSERVATION_PID, "1");
@ -132,6 +136,9 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
testObservationPersist.indexObservation(myObservation);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
}
private List<CodeableConcept> getCategoryCode() {
@ -172,13 +179,18 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
}
@Test
public void testIndexObservationMultiple() {
public void testIndexObservationMultiple() throws IOException {
indexMultipleObservations();
assertEquals(100, myResourceIndexedObservationLastNDao.count());
assertEquals(2, myCodeableConceptIndexedSearchParamNormalizedDao.count());
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.setLastNMax(100);
List<ObservationJson> observationDocuments = elasticsearchSvc.executeLastNWithAllFieldsForTest(searchParameterMap, myFhirCtx);
assertEquals(100, observationDocuments.size());
//List<CodeJson> codeDocuments = elasticsearchSvc.queryAllIndexedObservationCodesForTest();
//assertEquals(2, codeDocuments.size());
// Check that all observations were indexed.
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap = new SearchParameterMap();
searchParameterMap.add(Observation.SP_SUBJECT, multiSubjectParams);
searchParameterMap.setLastNMax(10);
@ -197,7 +209,7 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
}
private void indexMultipleObservations() {
private void indexMultipleObservations() throws IOException {
// Create two CodeableConcept values each for a Code with three codings.
CodeableConcept codeableConceptField1 = new CodeableConcept().setText("Test Codeable Concept Field for First Code");
@ -266,17 +278,23 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
multiSubjectParams = new ReferenceAndListParam().addAnd(subjectParams);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
}
@Test
public void testDeleteObservation() {
public void testDeleteObservation() throws IOException {
indexMultipleObservations();
assertEquals(100, myResourceIndexedObservationLastNDao.count());
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.setLastNMax(100);
List<ObservationJson> observationDocuments = elasticsearchSvc.executeLastNWithAllFieldsForTest(searchParameterMap, myFhirCtx);
assertEquals(100, observationDocuments.size());
// Check that fifth observation for fifth patient has been indexed.
ObservationIndexedSearchParamLastNEntity observation = myResourceIndexedObservationLastNDao.findByIdentifier("55");
ObservationJson observation = elasticsearchSvc.getObservationDocument("55");
assertNotNull(observation);
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap = new SearchParameterMap();
searchParameterMap.add(Observation.SP_SUBJECT, multiSubjectParams);
searchParameterMap.setLastNMax(10);
List<String> observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 200);
@ -290,10 +308,14 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
entity.setVersion(0L);
testObservationPersist.deleteObservationIndex(entity);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
// Confirm that observation was deleted.
assertEquals(99, myResourceIndexedObservationLastNDao.count());
observation = myResourceIndexedObservationLastNDao.findByIdentifier("55");
searchParameterMap = new SearchParameterMap();
searchParameterMap.setLastNMax(100);
observationDocuments = elasticsearchSvc.executeLastNWithAllFieldsForTest(searchParameterMap, myFhirCtx);
assertEquals(99, observationDocuments.size());
observation = elasticsearchSvc.getObservationDocument("55");
assertNull(observation);
observationIdsOnly = elasticsearchSvc.executeLastN(searchParameterMap, myFhirCtx, 200);
@ -303,14 +325,16 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
}
@Test
public void testUpdateObservation() {
public void testUpdateObservation() throws IOException {
indexSingleObservation();
ObservationIndexedSearchParamLastNEntity observationIndexEntity = myResourceIndexedObservationLastNDao.findAll().get(0);
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.setLastNMax(10);
ObservationJson observationIndexEntity = elasticsearchSvc.executeLastNWithAllFieldsForTest(searchParameterMap, myFhirCtx).get(0);
assertEquals(SINGLE_OBSERVATION_PID, observationIndexEntity.getIdentifier());
assertEquals(SINGLE_SUBJECT_ID, observationIndexEntity.getSubject());
assertEquals(SINGLE_EFFECTIVEDTM, observationIndexEntity.getEffectiveDtm());
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap = new SearchParameterMap();
ReferenceParam subjectParam = new ReferenceParam("Patient", "", SINGLE_SUBJECT_ID);
searchParameterMap.add(Observation.SP_SUBJECT, new ReferenceAndListParam().addAnd(new ReferenceOrListParam().addOr(subjectParam)));
TokenParam categoryParam = new TokenParam(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE);
@ -335,8 +359,9 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
updatedObservation.setCode(getObservationCode());
testObservationPersist.indexObservation(updatedObservation);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
ObservationIndexedSearchParamLastNEntity updatedObservationEntity = myResourceIndexedObservationLastNDao.findByIdentifier(SINGLE_OBSERVATION_PID);
ObservationJson updatedObservationEntity = elasticsearchSvc.getObservationDocument(SINGLE_OBSERVATION_PID);
assertEquals("1234", updatedObservationEntity.getSubject());
assertEquals(newEffectiveDtm.getValue(), updatedObservationEntity.getEffectiveDtm());
@ -398,6 +423,8 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
}
);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
SearchParameterMap searchParameterMap = new SearchParameterMap();
// execute Observation ID search - Composite Aggregation
@ -413,5 +440,4 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
}
}

View File

@ -4,12 +4,18 @@ import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CapabilityStatement;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.junit.AfterClass;
import org.junit.Test;
import java.util.Date;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.in;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@ -66,6 +72,34 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
}
@Test
public void testTransaction() {
Bundle input = new Bundle();
input.setType(Bundle.BundleType.TRANSACTION);
Organization org = new Organization();
org.setId(IdType.newRandomUuid());
org.setName("org");
input.addEntry()
.setFullUrl(org.getId())
.setResource(org)
.getRequest().setUrl("Organization").setMethod(Bundle.HTTPVerb.POST);
Patient p = new Patient();
p.getMeta().addTag("http://system", "code", "diisplay");
p.addName().setFamily("FAM");
p.addIdentifier().setSystem("system").setValue("value");
p.setBirthDate(new Date());
p.getManagingOrganization().setReference(org.getId());
input.addEntry()
.setFullUrl(p.getId())
.setResource(p)
.getRequest().setUrl("Patient").setMethod(Bundle.HTTPVerb.POST);
myTenantClientInterceptor.setTenantId(TENANT_A);
Bundle response = ourClient.transaction().withBundle(input).execute();
}
@AfterClass
public static void afterClassClearContext() {

View File

@ -12,8 +12,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.hl7.fhir.r4.model.Observation;
import org.junit.*;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
@ -57,6 +55,14 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
}
}
@After
public void after() throws IOException {
elasticsearchSvc.deleteAllDocumentsForTest(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocumentsForTest(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
}
@Test
public void testLastNAllPatientsQuery() {
@ -84,7 +90,7 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
searchParameterMap.add(Observation.SP_SUBJECT, buildReferenceAndListParam(subjectParam));
searchParameterMap.setLastNMax(3);
List<ObservationJson> observations = elasticsearchSvc.executeLastNWithAllFields(searchParameterMap, myFhirContext);
List<ObservationJson> observations = elasticsearchSvc.executeLastNWithAllFieldsForTest(searchParameterMap, myFhirContext);
assertEquals(60, observations.size());
@ -413,35 +419,33 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
private void createMultiplePatientsAndObservations() throws IOException {
// Create CodeableConcepts for two Codes, each with three codings.
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"));
CodeJson codeJson1 = new CodeJson(codeableConceptField1, codeableConceptId1);
String codeJson1Document = ourMapperNonPrettyPrint.writeValueAsString(codeJson1);
CodeJson codeJson1 = new CodeJson();
codeJson1.setCodeableConceptText("Test Codeable Concept Field for First Code");
codeJson1.setCodeableConceptId(codeableConceptId1);
codeJson1.addCoding("http://mycodes.org/fhir/observation-code", "test-code-1", "test-code-1 display");
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"));
CodeJson codeJson2 = new CodeJson(codeableConceptField2, codeableConceptId2);
String codeJson2Document = ourMapperNonPrettyPrint.writeValueAsString(codeJson2);
CodeJson codeJson2 = new CodeJson();
codeJson2.setCodeableConceptText("Test Codeable Concept Field for Second Code");
codeJson2.setCodeableConceptId(codeableConceptId1);
codeJson2.addCoding("http://mycodes.org/fhir/observation-code", "test-code-2", "test-code-2 display");
// Create CodeableConcepts for two categories, each with three codings.
List<Coding> category1 = new ArrayList<>();
// Create three codings and first category CodeableConcept
category1.add(new Coding("http://mycodes.org/fhir/observation-category", "test-heart-rate", "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"));
List<CodeableConcept> categoryConcepts1 = new ArrayList<>();
CodeableConcept categoryCodeableConcept1 = new CodeableConcept().setText("Test Codeable Concept Field for first category");
categoryCodeableConcept1.setCoding(category1);
List<CodeJson> categoryConcepts1 = new ArrayList<>();
CodeJson categoryCodeableConcept1 = new CodeJson();
categoryCodeableConcept1.setCodeableConceptText("Test Codeable Concept Field for first category");
categoryCodeableConcept1.addCoding("http://mycodes.org/fhir/observation-category", "test-heart-rate", "test-heart-rate display");
categoryCodeableConcept1.addCoding("http://myalternatecodes.org/fhir/observation-category", "test-alt-heart-rate", "test-alt-heart-rate display");
categoryCodeableConcept1.addCoding("http://mysecondaltcodes.org/fhir/observation-category", "test-2nd-alt-heart-rate", "test-2nd-alt-heart-rate display");
categoryConcepts1.add(categoryCodeableConcept1);
// Create three codings and second category CodeableConcept
List<Coding> category2 = new ArrayList<>();
category2.add(new Coding("http://mycodes.org/fhir/observation-category", "test-vital-signs", "test-vital-signs display"));
category2.add(new Coding("http://myalternatecodes.org/fhir/observation-category", "test-alt-vitals", "test-alt-vitals display"));
category2.add(new Coding("http://mysecondaltcodes.org/fhir/observation-category", "test-2nd-alt-vitals", "test-2nd-alt-vitals display"));
List<CodeableConcept> categoryConcepts2 = new ArrayList<>();
CodeableConcept categoryCodeableConcept2 = new CodeableConcept().setText("Test Codeable Concept Field for second category");
categoryCodeableConcept2.setCoding(category2);
List<CodeJson> categoryConcepts2 = new ArrayList<>();
CodeJson categoryCodeableConcept2 = new CodeJson();
categoryCodeableConcept2.setCodeableConceptText("Test Codeable Concept Field for second category");
categoryCodeableConcept2.addCoding("http://mycodes.org/fhir/observation-category", "test-vital-signs", "test-vital-signs display");
categoryCodeableConcept2.addCoding("http://myalternatecodes.org/fhir/observation-category", "test-alt-vitals", "test-alt-vitals display");
categoryCodeableConcept2.addCoding("http://mysecondaltcodes.org/fhir/observation-category", "test-2nd-alt-vitals", "test-2nd-alt-vitals display");
categoryConcepts2.add(categoryCodeableConcept2);
for (int patientCount = 0; patientCount < 10; patientCount++) {
@ -457,21 +461,20 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
if (entryCount % 2 == 1) {
observationJson.setCategories(categoryConcepts1);
observationJson.setCode(codeableConceptField1);
observationJson.setCode(codeJson1);
observationJson.setCode_concept_id(codeableConceptId1);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX, codeableConceptId1, codeJson1Document, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE));
assertTrue(elasticsearchSvc.createOrUpdateObservationCodeIndex(codeableConceptId1, codeJson1));
} else {
observationJson.setCategories(categoryConcepts2);
observationJson.setCode(codeableConceptField2);
observationJson.setCode(codeJson2);
observationJson.setCode_concept_id(codeableConceptId2);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX, codeableConceptId2, codeJson2Document, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE));
assertTrue(elasticsearchSvc.createOrUpdateObservationCodeIndex(codeableConceptId2, codeJson2));
}
Date effectiveDtm = new Date(baseObservationDate.getTimeInMillis() - ((10-entryCount)*3600*1000));
observationJson.setEffectiveDtm(effectiveDtm);
String observationDocument = ourMapperNonPrettyPrint.writeValueAsString(observationJson);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX, identifier, observationDocument, ElasticsearchSvcImpl.OBSERVATION_DOCUMENT_TYPE));
assertTrue(elasticsearchSvc.createOrUpdateObservationIndex(identifier, observationJson));
if (createdPatientObservationMap.containsKey(subject)) {
Map<String, List<Date>> observationCodeMap = createdPatientObservationMap.get(subject);
@ -498,11 +501,8 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
}
}
try {
Thread.sleep(2000L);
} catch (InterruptedException theE) {
theE.printStackTrace();
}
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
}
@ -510,7 +510,7 @@ public class LastNElasticsearchSvcMultipleObservationsIT {
public void testLastNNoParamsQuery() {
SearchParameterMap searchParameterMap = new SearchParameterMap();
searchParameterMap.setLastNMax(1);
List<ObservationJson> observations = elasticsearchSvc.executeLastNWithAllFields(searchParameterMap, myFhirContext);
List<ObservationJson> observations = elasticsearchSvc.executeLastNWithAllFieldsForTest(searchParameterMap, myFhirContext);
assertEquals(2, observations.size());

View File

@ -19,8 +19,6 @@ 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.After;
import org.junit.BeforeClass;
import org.junit.Test;
@ -94,8 +92,10 @@ public class LastNElasticsearchSvcSingleObservationIT {
@After
public void after() throws IOException {
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocuments(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
elasticsearchSvc.deleteAllDocumentsForTest(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.deleteAllDocumentsForTest(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
}
@Test
@ -121,7 +121,7 @@ public class LastNElasticsearchSvcSingleObservationIT {
assertEquals(RESOURCEPID, observationIdsOnly.get(0));
// execute Observation search for all search fields
List<ObservationJson> observations = elasticsearchSvc.executeLastNWithAllFields(searchParameterMap, myFhirContext);
List<ObservationJson> observations = elasticsearchSvc.executeLastNWithAllFieldsForTest(searchParameterMap, myFhirContext);
validateFullObservationSearch(observations);
}
@ -229,7 +229,7 @@ public class LastNElasticsearchSvcSingleObservationIT {
assertEquals(String.valueOf(CodeSystemHash.hashCodeSystem(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE)), code_coding_code_system_hash);
// Retrieve all Observation codes
List<CodeJson> codes = elasticsearchSvc.queryAllIndexedObservationCodes();
List<CodeJson> codes = elasticsearchSvc.queryAllIndexedObservationCodesForTest();
assertEquals(1, codes.size());
CodeJson persistedObservationCode = codes.get(0);
@ -264,52 +264,45 @@ public class LastNElasticsearchSvcSingleObservationIT {
indexedObservation.setEffectiveDtm(EFFECTIVEDTM);
// Add three CodeableConcepts for category
List<CodeableConcept> categoryConcepts = new ArrayList<>();
List<CodeJson> categoryConcepts = new ArrayList<>();
// Create three codings and first category CodeableConcept
List<Coding> category1 = new ArrayList<>();
CodeableConcept categoryCodeableConcept1 = new CodeableConcept().setText(FIRSTCATEGORYTEXT);
category1.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE, FIRSTCATEGORYFIRSTCODINGDISPLAY));
category1.add(new Coding(CATEGORYSECONDCODINGSYSTEM, FIRSTCATEGORYSECONDCODINGCODE, FIRSTCATEGORYSECONDCODINGDISPLAY));
category1.add(new Coding(CATEGORYTHIRDCODINGSYSTEM, FIRSTCATEGORYTHIRDCODINGCODE, FIRSTCATEGORYTHIRDCODINGDISPLAY));
categoryCodeableConcept1.setCoding(category1);
CodeJson categoryCodeableConcept1 = new CodeJson();
categoryCodeableConcept1.setCodeableConceptText(FIRSTCATEGORYTEXT);
categoryCodeableConcept1.addCoding(CATEGORYFIRSTCODINGSYSTEM, FIRSTCATEGORYFIRSTCODINGCODE, FIRSTCATEGORYFIRSTCODINGDISPLAY);
categoryCodeableConcept1.addCoding(CATEGORYSECONDCODINGSYSTEM, FIRSTCATEGORYSECONDCODINGCODE, FIRSTCATEGORYSECONDCODINGDISPLAY);
categoryCodeableConcept1.addCoding(CATEGORYTHIRDCODINGSYSTEM, FIRSTCATEGORYTHIRDCODINGCODE, FIRSTCATEGORYTHIRDCODINGDISPLAY);
categoryConcepts.add(categoryCodeableConcept1);
// Create three codings and second category CodeableConcept
List<Coding> category2 = new ArrayList<>();
CodeableConcept categoryCodeableConcept2 = new CodeableConcept().setText(SECONDCATEGORYTEXT);
category2.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, SECONDCATEGORYFIRSTCODINGCODE, SECONDCATEGORYFIRSTCODINGDISPLAY));
category2.add(new Coding(CATEGORYSECONDCODINGSYSTEM, SECONDCATEGORYSECONDCODINGCODE, SECONDCATEGORYSECONDCODINGDISPLAY));
category2.add(new Coding(CATEGORYTHIRDCODINGSYSTEM, SECONDCATEGORYTHIRDCODINGCODE, SECONDCATEGORYTHIRDCODINGDISPLAY));
categoryCodeableConcept2.setCoding(category2);
CodeJson categoryCodeableConcept2 = new CodeJson();
categoryCodeableConcept2.setCodeableConceptText(SECONDCATEGORYTEXT);
categoryCodeableConcept2.addCoding(CATEGORYFIRSTCODINGSYSTEM, SECONDCATEGORYFIRSTCODINGCODE, SECONDCATEGORYFIRSTCODINGDISPLAY);
categoryCodeableConcept2.addCoding(CATEGORYSECONDCODINGSYSTEM, SECONDCATEGORYSECONDCODINGCODE, SECONDCATEGORYSECONDCODINGDISPLAY);
categoryCodeableConcept2.addCoding(CATEGORYTHIRDCODINGSYSTEM, SECONDCATEGORYTHIRDCODINGCODE, SECONDCATEGORYTHIRDCODINGDISPLAY);
categoryConcepts.add(categoryCodeableConcept2);
// Create three codings and third category CodeableConcept
List<Coding> category3 = new ArrayList<>();
CodeableConcept categoryCodeableConcept3 = new CodeableConcept().setText(THIRDCATEGORYTEXT);
category3.add(new Coding(CATEGORYFIRSTCODINGSYSTEM, THIRDCATEGORYFIRSTCODINGCODE, THIRDCATEGORYFIRSTCODINGDISPLAY));
category3.add(new Coding(CATEGORYSECONDCODINGSYSTEM, THIRDCATEGORYSECONDCODINGCODE, THIRDCATEGORYSECONDCODINGDISPLAY));
category3.add(new Coding(CATEGORYTHIRDCODINGSYSTEM, THIRDCATEGORYTHIRDCODINGCODE, THIRDCATEGORYTHIRDCODINGDISPLAY));
categoryCodeableConcept3.setCoding(category3);
CodeJson categoryCodeableConcept3 = new CodeJson();
categoryCodeableConcept3.setCodeableConceptText(THIRDCATEGORYTEXT);
categoryCodeableConcept3.addCoding(CATEGORYFIRSTCODINGSYSTEM, THIRDCATEGORYFIRSTCODINGCODE, THIRDCATEGORYFIRSTCODINGDISPLAY);
categoryCodeableConcept3.addCoding(CATEGORYSECONDCODINGSYSTEM, THIRDCATEGORYSECONDCODINGCODE, THIRDCATEGORYSECONDCODINGDISPLAY);
categoryCodeableConcept3.addCoding(CATEGORYTHIRDCODINGSYSTEM, THIRDCATEGORYTHIRDCODINGCODE, THIRDCATEGORYTHIRDCODINGDISPLAY);
categoryConcepts.add(categoryCodeableConcept3);
indexedObservation.setCategories(categoryConcepts);
// Create CodeableConcept for Code with three codings.
indexedObservation.setCode_concept_id(OBSERVATIONSINGLECODEID);
CodeableConcept codeableConceptField = new CodeableConcept().setText(OBSERVATIONCODETEXT);
codeableConceptField.addCoding(new Coding(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE, CODEFIRSTCODINGDISPLAY));
CodeJson codeableConceptField = new CodeJson();
codeableConceptField.setCodeableConceptText(OBSERVATIONCODETEXT);
codeableConceptField.addCoding(CODEFIRSTCODINGSYSTEM, CODEFIRSTCODINGCODE, CODEFIRSTCODINGDISPLAY);
indexedObservation.setCode(codeableConceptField);
String observationDocument = ourMapperNonPrettyPrint.writeValueAsString(indexedObservation);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX, RESOURCEPID, observationDocument, ElasticsearchSvcImpl.OBSERVATION_DOCUMENT_TYPE));
assertTrue(elasticsearchSvc.createOrUpdateObservationIndex(RESOURCEPID, indexedObservation));
CodeJson observationCode = new CodeJson(codeableConceptField, OBSERVATIONSINGLECODEID);
String codeDocument = ourMapperNonPrettyPrint.writeValueAsString(observationCode);
assertTrue(elasticsearchSvc.performIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX, OBSERVATIONSINGLECODEID, codeDocument, ElasticsearchSvcImpl.CODE_DOCUMENT_TYPE));
try {
Thread.sleep(1000L);
} catch (InterruptedException theE) {
theE.printStackTrace();
}
codeableConceptField.setCodeableConceptId(OBSERVATIONSINGLECODEID);
assertTrue(elasticsearchSvc.createOrUpdateObservationCodeIndex(OBSERVATIONSINGLECODEID, codeableConceptField));
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_CODE_INDEX);
}
}

View File

@ -34,6 +34,7 @@ import org.springframework.stereotype.Service;
@Service
public class EmpiSearchParameterLoader {
public static final String EMPI_PERSON_ASSURANCE_SEARCH_PARAMETER_ID = "person-assurance";
public static final String EMPI_PERSON_ACTIVE_SEARCH_PARAMETER_ID = "person-active";
@Autowired
public FhirContext myFhirContext;
@Autowired
@ -41,12 +42,15 @@ public class EmpiSearchParameterLoader {
synchronized public void daoUpdateEmpiSearchParameters() {
IBaseResource personAssurance;
IBaseResource personActive;
switch (myFhirContext.getVersion().getVersion()) {
case DSTU3:
personAssurance = buildEmpiSearchParameterDstu3();
personAssurance = buildAssuranceEmpiSearchParameterDstu3();
personActive = buildActiveEmpiSearchParameterDstu3();
break;
case R4:
personAssurance = buildEmpiSearchParameterR4();
personAssurance = buildAssuranceEmpiSearchParameterR4();
personActive = buildActiveEmpiSearchParameterR4();
break;
default:
throw new ConfigurationException("EMPI not supported for FHIR version " + myFhirContext.getVersion().getVersion());
@ -54,9 +58,10 @@ public class EmpiSearchParameterLoader {
IFhirResourceDao<IBaseResource> searchParameterDao = myDaoRegistry.getResourceDao("SearchParameter");
searchParameterDao.update(personAssurance);
searchParameterDao.update(personActive);
}
private org.hl7.fhir.dstu3.model.SearchParameter buildEmpiSearchParameterDstu3() {
private org.hl7.fhir.dstu3.model.SearchParameter buildAssuranceEmpiSearchParameterDstu3() {
org.hl7.fhir.dstu3.model.SearchParameter retval = new org.hl7.fhir.dstu3.model.SearchParameter();
retval.setId(EMPI_PERSON_ASSURANCE_SEARCH_PARAMETER_ID);
retval.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
@ -69,7 +74,7 @@ public class EmpiSearchParameterLoader {
return retval;
}
private SearchParameter buildEmpiSearchParameterR4() {
private SearchParameter buildAssuranceEmpiSearchParameterR4() {
SearchParameter retval = new SearchParameter();
retval.setId(EMPI_PERSON_ASSURANCE_SEARCH_PARAMETER_ID);
retval.setStatus(Enumerations.PublicationStatus.ACTIVE);
@ -81,4 +86,30 @@ public class EmpiSearchParameterLoader {
retval.setExpression("Person.link.assurance");
return retval;
}
private org.hl7.fhir.dstu3.model.SearchParameter buildActiveEmpiSearchParameterDstu3() {
org.hl7.fhir.dstu3.model.SearchParameter retval = new org.hl7.fhir.dstu3.model.SearchParameter();
retval.setId(EMPI_PERSON_ACTIVE_SEARCH_PARAMETER_ID);
retval.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED);
retval.setCode("active");
retval.addBase("Person");
retval.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN);
retval.setDescription("The active status of a Person");
retval.setExpression("Person.active");
return retval;
}
private SearchParameter buildActiveEmpiSearchParameterR4() {
SearchParameter retval = new SearchParameter();
retval.setId(EMPI_PERSON_ACTIVE_SEARCH_PARAMETER_ID);
retval.setStatus(Enumerations.PublicationStatus.ACTIVE);
retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED);
retval.setCode("active");
retval.addBase("Person");
retval.setType(Enumerations.SearchParamType.TOKEN);
retval.setDescription("The active status of a Person");
retval.setExpression("Person.active");
return retval;
}
}

View File

@ -26,6 +26,7 @@ import ca.uhn.fhir.empi.api.IEmpiSettings;
import ca.uhn.fhir.empi.model.CanonicalEID;
import ca.uhn.fhir.empi.util.EIDHelper;
import ca.uhn.fhir.empi.util.EmpiUtil;
import ca.uhn.fhir.empi.util.PersonHelper;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
@ -56,6 +57,8 @@ public class EmpiStorageInterceptor implements IEmpiStorageInterceptor {
private EIDHelper myEIDHelper;
@Autowired
private IEmpiSettings myEmpiSettings;
@Autowired
private PersonHelper myPersonHelper;
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
public void blockManualPersonManipulationOnCreate(IBaseResource theBaseResource, RequestDetails theRequestDetails, ServletRequestDetails theServletRequestDetails) {
@ -80,6 +83,12 @@ public class EmpiStorageInterceptor implements IEmpiStorageInterceptor {
forbidIfHasMultipleEids(theNewResource);
}
if (EmpiUtil.isEmpiManagedPerson(myFhirContext, theNewResource) &&
myPersonHelper.isDeactivated(theNewResource)) {
ourLog.debug("Deleting empi links to deactivated Person {}", theNewResource.getIdElement().toUnqualifiedVersionless());
myEmpiLinkDaoSvc.deleteWithAnyReferenceTo(theNewResource);
}
if (isInternalRequest(theRequestDetails)) {
return;
}

View File

@ -99,7 +99,6 @@ public class EmpiLinkSvcImpl implements IEmpiLinkSvc {
.map(this::personLinkFromEmpiLink)
.collect(Collectors.toList());
myPersonHelper.setLinks(thePersonResource, newLinks);
myEmpiResourceDaoSvc.updatePerson(thePersonResource);
if (newLinks.size() > origLinkCount) {
log(theEmpiTransactionContext, thePersonResource.getIdElement().toVersionless() + " links increased from " + origLinkCount + " to " + newLinks.size());
} else if (newLinks.size() < origLinkCount) {

View File

@ -51,31 +51,17 @@ public class EmpiLinkUpdaterSvcImpl implements IEmpiLinkUpdaterSvc {
EmpiLinkDaoSvc myEmpiLinkDaoSvc;
@Autowired
IEmpiLinkSvc myEmpiLinkSvc;
@Autowired
EmpiResourceDaoSvc myEmpiResourceDaoSvc;
@Autowired
EmpiMatchLinkSvc myEmpiMatchLinkSvc;
@Transactional
@Override
public IAnyResource updateLink(IAnyResource thePerson, IAnyResource theTarget, EmpiMatchResultEnum theMatchResult, EmpiTransactionContext theEmpiContext) {
if (theMatchResult != EmpiMatchResultEnum.NO_MATCH &&
theMatchResult != EmpiMatchResultEnum.MATCH) {
throw new InvalidRequestException("Match Result may only be set to " + EmpiMatchResultEnum.NO_MATCH + " or " + EmpiMatchResultEnum.MATCH);
}
String personType = myFhirContext.getResourceType(thePerson);
if (!"Person".equals(personType)) {
throw new InvalidRequestException("First argument to updateLink must be a Person. Was " + personType);
}
String targetType = myFhirContext.getResourceType(theTarget);
if (!EmpiUtil.supportedTargetType(targetType)) {
throw new InvalidRequestException("Second argument to updateLink must be a Patient or Practitioner. Was " + targetType);
}
if (!EmpiUtil.isEmpiManaged(thePerson)) {
throw new InvalidRequestException("Only EMPI Managed Person resources may be updated via this operation. The Person resource provided is not tagged as managed by hapi-empi");
}
if (!EmpiUtil.isEmpiAccessible(theTarget)) {
throw new InvalidRequestException("The target is marked with the " + EmpiConstants.CODE_NO_EMPI_MANAGED + " tag which means it may not be EMPI linked.");
}
validateUpdateLinkRequest(thePerson, theTarget, theMatchResult, targetType);
Long personId = myIdHelperService.getPidOrThrowException(thePerson);
Long targetId = myIdHelperService.getPidOrThrowException(theTarget);
@ -95,6 +81,34 @@ public class EmpiLinkUpdaterSvcImpl implements IEmpiLinkUpdaterSvc {
empiLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
myEmpiLinkDaoSvc.save(empiLink);
myEmpiLinkSvc.syncEmpiLinksToPersonLinks(thePerson, theEmpiContext);
myEmpiResourceDaoSvc.updatePerson(thePerson);
if (theMatchResult == EmpiMatchResultEnum.NO_MATCH) {
// Need to find a new Person to link this target to
myEmpiMatchLinkSvc.updateEmpiLinksForEmpiTarget(theTarget, theEmpiContext);
}
return thePerson;
}
private void validateUpdateLinkRequest(IAnyResource thePerson, IAnyResource theTarget, EmpiMatchResultEnum theMatchResult, String theTargetType) {
String personType = myFhirContext.getResourceType(thePerson);
if (theMatchResult != EmpiMatchResultEnum.NO_MATCH &&
theMatchResult != EmpiMatchResultEnum.MATCH) {
throw new InvalidRequestException("Match Result may only be set to " + EmpiMatchResultEnum.NO_MATCH + " or " + EmpiMatchResultEnum.MATCH);
}
if (!"Person".equals(personType)) {
throw new InvalidRequestException("First argument to updateLink must be a Person. Was " + personType);
}
if (!EmpiUtil.supportedTargetType(theTargetType)) {
throw new InvalidRequestException("Second argument to updateLink must be a Patient or Practitioner. Was " + theTargetType);
}
if (!EmpiUtil.isEmpiManaged(thePerson)) {
throw new InvalidRequestException("Only EMPI Managed Person resources may be updated via this operation. The Person resource provided is not tagged as managed by hapi-empi");
}
if (!EmpiUtil.isEmpiAccessible(theTarget)) {
throw new InvalidRequestException("The target is marked with the " + EmpiConstants.CODE_NO_EMPI_MANAGED + " tag which means it may not be EMPI linked.");
}
}
}

View File

@ -55,28 +55,30 @@ public class EmpiPersonMergerSvcImpl implements IEmpiPersonMergerSvc {
@Override
@Transactional
public IAnyResource mergePersons(IAnyResource thePersonToDelete, IAnyResource thePersonToKeep, EmpiTransactionContext theEmpiTransactionContext) {
public IAnyResource mergePersons(IAnyResource theFromPerson, IAnyResource theToPerson, EmpiTransactionContext theEmpiTransactionContext) {
// TODO EMPI replace this with a post containing the manually merged fields
myPersonHelper.mergePersonFields(thePersonToDelete, thePersonToKeep);
mergeLinks(thePersonToDelete, thePersonToKeep, theEmpiTransactionContext);
myEmpiResourceDaoSvc.updatePerson(thePersonToKeep);
log(theEmpiTransactionContext, "Merged " + thePersonToDelete.getIdElement().toVersionless() + " into " + thePersonToKeep.getIdElement().toVersionless());
myEmpiResourceDaoSvc.deletePerson(thePersonToDelete);
log(theEmpiTransactionContext, "Deleted " + thePersonToDelete.getIdElement().toVersionless());
return thePersonToKeep;
myPersonHelper.mergePersonFields(theFromPerson, theToPerson);
mergeLinks(theFromPerson, theToPerson, theEmpiTransactionContext);
myEmpiResourceDaoSvc.updatePerson(theToPerson);
log(theEmpiTransactionContext, "Merged " + theFromPerson.getIdElement().toVersionless() + " into " + theToPerson.getIdElement().toVersionless());
myPersonHelper.deactivatePerson(theFromPerson);
myEmpiResourceDaoSvc.updatePerson(theFromPerson);
log(theEmpiTransactionContext, "Deactivated " + theFromPerson.getIdElement().toVersionless());
return theToPerson;
}
private void mergeLinks(IAnyResource thePersonToDelete, IAnyResource thePersonToKeep, EmpiTransactionContext theEmpiTransactionContext) {
long personToKeepPid = myIdHelperService.getPidOrThrowException(thePersonToKeep);
List<EmpiLink> incomingLinks = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(thePersonToDelete);
List<EmpiLink> origLinks = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(thePersonToKeep);
private void mergeLinks(IAnyResource theFromPerson, IAnyResource theToPerson, EmpiTransactionContext theEmpiTransactionContext) {
long toPersonPid = myIdHelperService.getPidOrThrowException(theToPerson);
List<EmpiLink> incomingLinks = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(theFromPerson);
List<EmpiLink> origLinks = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(theToPerson);
// For each incomingLink, either ignore it, move it, or replace the original one
for (EmpiLink incomingLink : incomingLinks) {
Optional<EmpiLink> optionalOrigLink = findLinkWithMatchingTarget(origLinks, incomingLink);
if (optionalOrigLink.isPresent()) {
// The original links already contain this target, so move it over to the personToKeep
// The original links already contain this target, so move it over to the toPerson
EmpiLink origLink = optionalOrigLink.get();
if (incomingLink.isManual()) {
switch (origLink.getLinkSource()) {
@ -94,14 +96,14 @@ public class EmpiPersonMergerSvcImpl implements IEmpiPersonMergerSvc {
continue;
}
}
// The original links didn't contain this target, so move it over to the personToKeep
incomingLink.setPersonPid(personToKeepPid);
// The original links didn't contain this target, so move it over to the toPerson
incomingLink.setPersonPid(toPersonPid);
ourLog.trace("Saving link {}", incomingLink);
myEmpiLinkDaoSvc.save(incomingLink);
}
myEmpiLinkSvc.syncEmpiLinksToPersonLinks(thePersonToDelete, theEmpiTransactionContext);
myEmpiLinkSvc.syncEmpiLinksToPersonLinks(thePersonToKeep, theEmpiTransactionContext);
myEmpiLinkSvc.syncEmpiLinksToPersonLinks(theFromPerson, theEmpiTransactionContext);
myEmpiLinkSvc.syncEmpiLinksToPersonLinks(theToPerson, theEmpiTransactionContext);
}
private Optional<EmpiLink> findLinkWithMatchingTarget(List<EmpiLink> theEmpiLinks, EmpiLink theLinkWithTargetToMatch) {

View File

@ -86,7 +86,4 @@ public class EmpiResourceDaoSvc {
}
}
public void deletePerson(IAnyResource thePersonToDelete) {
myPersonDao.delete(thePersonToDelete.getIdElement());
}
}

View File

@ -14,6 +14,7 @@ import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
import ca.uhn.fhir.jpa.dao.data.IEmpiLinkDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.empi.config.EmpiConsumerConfig;
import ca.uhn.fhir.jpa.empi.config.EmpiSearchParameterLoader;
import ca.uhn.fhir.jpa.empi.config.EmpiSubmitterConfig;
import ca.uhn.fhir.jpa.empi.config.TestEmpiConfigR4;
import ca.uhn.fhir.jpa.empi.matcher.IsLinkedTo;
@ -25,11 +26,13 @@ import ca.uhn.fhir.jpa.empi.matcher.IsSamePersonAs;
import ca.uhn.fhir.jpa.empi.svc.EmpiMatchLinkSvc;
import ca.uhn.fhir.jpa.entity.EmpiLink;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import org.hamcrest.Matcher;
import org.hl7.fhir.instance.model.api.IAnyResource;
@ -39,7 +42,6 @@ import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Person;
import org.hl7.fhir.r4.model.Practitioner;
import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
@ -93,6 +95,10 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
protected EmpiMatchLinkSvc myEmpiMatchLinkSvc;
@Autowired
protected EIDHelper myEIDHelper;
@Autowired
EmpiSearchParameterLoader myEmpiSearchParameterLoader;
@Autowired
SearchParamRegistryImpl mySearchParamRegistry;
protected ServletRequestDetails myRequestDetails = new ServletRequestDetails(null);
@ -126,6 +132,7 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
protected Person createPerson(Person thePerson, boolean theEmpiManaged) {
if (theEmpiManaged) {
thePerson.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED);
thePerson.setActive(true);
}
DaoMethodOutcome outcome = myPersonDao.create(thePerson);
Person person = (Person) outcome.getResource();
@ -311,16 +318,29 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
return IsMatchedToAPerson.matchedToAPerson(myIdHelperService, myEmpiLinkDaoSvc);
}
protected Person getOnlyPerson() {
List<IBaseResource> resources = getAllPersons();
protected Person getOnlyActivePerson() {
List<IBaseResource> resources = getAllActivePersons();
assertEquals(1, resources.size());
return (Person) resources.get(0);
}
@NotNull
@Nonnull
protected List<IBaseResource> getAllActivePersons() {
return getAllPersons(true);
}
@Nonnull
protected List<IBaseResource> getAllPersons() {
return getAllPersons(false);
}
@Nonnull
private List<IBaseResource> getAllPersons(boolean theOnlyActive) {
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
if (theOnlyActive) {
map.add("active", new TokenParam().setValue("true"));
}
IBundleProvider bundle = myPersonDao.search(map);
return bundle.getResources(0, 999);
}
@ -337,4 +357,9 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
empiLink.setTargetPid(myIdHelperService.getPidOrNull(patient));
return empiLink;
}
protected void loadEmpiSearchParameters() {
myEmpiSearchParameterLoader.daoUpdateEmpiSearchParameters();
mySearchParamRegistry.forceRefresh();
}
}

View File

@ -20,6 +20,7 @@ import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Person;
import org.hl7.fhir.r4.model.Practitioner;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.Logger;
@ -54,6 +55,11 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
@Autowired
private IdHelperService myIdHelperService;
@Before
public void before() {
super.loadEmpiSearchParameters();
}
@Test
public void testCreatePatient() throws InterruptedException {
myEmpiHelper.createWithLatch(new Patient());
@ -76,7 +82,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
public void testDeletePersonDeletesLinks() throws InterruptedException {
myEmpiHelper.createWithLatch(new Patient());
assertLinkCount(1);
Person person = getOnlyPerson();
Person person = getOnlyActivePerson();
myPersonDao.delete(person.getIdElement());
assertLinkCount(0);
}

View File

@ -9,39 +9,49 @@ import org.hl7.fhir.r4.model.StringType;
import org.junit.Before;
import javax.annotation.Nonnull;
import java.util.List;
import static org.junit.Assert.assertEquals;
public abstract class BaseLinkR4Test extends BaseProviderR4Test {
protected static final StringType NO_MATCH_RESULT = new StringType(EmpiMatchResultEnum.NO_MATCH.name());
protected static final StringType MATCH_RESULT = new StringType(EmpiMatchResultEnum.MATCH.name());
protected static final StringType POSSIBLE_MATCH_RESULT = new StringType(EmpiMatchResultEnum.POSSIBLE_MATCH.name());
protected static final StringType POSSIBLE_DUPLICATE_RESULT = new StringType(EmpiMatchResultEnum.POSSIBLE_DUPLICATE.name());
protected Patient myPatient;
protected Person myPerson;
protected EmpiLink myLink;
protected StringType myPatientId;
protected StringType myPersonId;
protected StringType myNoMatch;
protected StringType myPossibleMatch;
protected StringType myPossibleDuplicate;
protected StringType myVersionlessPersonId;
@Before
public void before() {
super.before();
myPatient = createPatientAndUpdateLinks(new Patient());
myPatientId = new StringType(myPatient.getIdElement().toUnqualifiedVersionless().getValue());
myPatientId = new StringType(myPatient.getIdElement().getValue());
myPerson = getPersonFromTarget(myPatient);
myPersonId = new StringType(myPerson.getIdElement().toUnqualifiedVersionless().getValue());
myLink = getLink();
assertEquals(EmpiLinkSourceEnum.AUTO, myLink.getLinkSource());
assertEquals(EmpiMatchResultEnum.MATCH, myLink.getMatchResult());
myPersonId = new StringType(myPerson.getIdElement().getValue());
myVersionlessPersonId = new StringType(myPerson.getIdElement().toVersionless().getValue());
myNoMatch = new StringType(EmpiMatchResultEnum.NO_MATCH.name());
myPossibleMatch = new StringType(EmpiMatchResultEnum.POSSIBLE_MATCH.name());
myPossibleDuplicate = new StringType(EmpiMatchResultEnum.POSSIBLE_DUPLICATE.name());
myLink = getOnlyPatientLink();
// Tests require our initial link to be a POSSIBLE_MATCH
myLink.setMatchResult(EmpiMatchResultEnum.POSSIBLE_MATCH);
myEmpiLinkDao.save(myLink);
assertEquals(EmpiLinkSourceEnum.AUTO, myLink.getLinkSource());
}
@Nonnull
protected EmpiLink getLink() {
protected EmpiLink getOnlyPatientLink() {
return myEmpiLinkDaoSvc.findEmpiLinkByTarget(myPatient).get();
}
@Nonnull
protected List<EmpiLink> getPatientLinks() {
return myEmpiLinkDaoSvc.findEmpiLinksByTarget(myPatient);
}
}

View File

@ -16,19 +16,20 @@ import static org.junit.Assert.fail;
public class EmpiProviderMergePersonsR4Test extends BaseProviderR4Test {
private Person myDeletePerson;
private StringType myDeletePersonId;
private Person myKeepPerson;
private StringType myKeepPersonId;
private Person myFromPerson;
private StringType myFromPersonId;
private Person myToPerson;
private StringType myToPersonId;
@Before
public void before() {
super.before();
super.loadEmpiSearchParameters();
myDeletePerson = createPerson();
myDeletePersonId = new StringType(myDeletePerson.getIdElement().toUnqualifiedVersionless().getValue());
myKeepPerson = createPerson();
myKeepPersonId = new StringType(myKeepPerson.getIdElement().toUnqualifiedVersionless().getValue());
myFromPerson = createPerson();
myFromPersonId = new StringType(myFromPerson.getIdElement().getValue());
myToPerson = createPerson();
myToPersonId = new StringType(myToPerson.getIdElement().getValue());
}
@Test
@ -45,18 +46,19 @@ public class EmpiProviderMergePersonsR4Test extends BaseProviderR4Test {
@Test
public void testMerge() {
Person mergedPerson = myEmpiProviderR4.mergePersons(myDeletePersonId, myKeepPersonId, myRequestDetails);
assertEquals(myKeepPerson.getIdElement(), mergedPerson.getIdElement());
Person mergedPerson = myEmpiProviderR4.mergePersons(myFromPersonId, myToPersonId, myRequestDetails);
assertEquals(myToPerson.getIdElement(), mergedPerson.getIdElement());
assertThat(mergedPerson, is(samePersonAs(mergedPerson)));
assertEquals(1, getAllPersons().size());
assertEquals(2, getAllPersons().size());
assertEquals(1, getAllActivePersons().size());
}
@Test
public void testUnmanagedMerge() {
StringType deletePersonId = new StringType(createUnmanagedPerson().getIdElement().toVersionless().getValue());
StringType keepPersonId = new StringType(createUnmanagedPerson().getIdElement().toVersionless().getValue());
StringType fromPersonId = new StringType(createUnmanagedPerson().getIdElement().getValue());
StringType toPersonId = new StringType(createUnmanagedPerson().getIdElement().getValue());
try {
myEmpiProviderR4.mergePersons(deletePersonId, keepPersonId, myRequestDetails);
myEmpiProviderR4.mergePersons(fromPersonId, toPersonId, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("Only EMPI managed resources can be merged. Empi managed resource have the HAPI-EMPI tag.", e.getMessage());
@ -66,12 +68,12 @@ public class EmpiProviderMergePersonsR4Test extends BaseProviderR4Test {
@Test
public void testMergePatients() {
try {
StringType patientId = new StringType(createPatient().getIdElement().toVersionless().getValue());
StringType otherPatientId = new StringType(createPatient().getIdElement().toVersionless().getValue());
StringType patientId = new StringType(createPatient().getIdElement().getValue());
StringType otherPatientId = new StringType(createPatient().getIdElement().getValue());
myEmpiProviderR4.mergePersons(patientId, otherPatientId, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("personIdToDelete must have form Person/<id> where <id> is the id of the person", e.getMessage());
assertEquals("fromPersonId must have form Person/<id> where <id> is the id of the person", e.getMessage());
}
}
@ -82,50 +84,50 @@ public class EmpiProviderMergePersonsR4Test extends BaseProviderR4Test {
myEmpiProviderR4.mergePersons(null, null, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("personIdToDelete cannot be null", e.getMessage());
assertEquals("fromPersonId cannot be null", e.getMessage());
}
try {
myEmpiProviderR4.mergePersons(null, myKeepPersonId, myRequestDetails);
myEmpiProviderR4.mergePersons(null, myToPersonId, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("personIdToDelete cannot be null", e.getMessage());
assertEquals("fromPersonId cannot be null", e.getMessage());
}
try {
myEmpiProviderR4.mergePersons(myDeletePersonId, null, myRequestDetails);
myEmpiProviderR4.mergePersons(myFromPersonId, null, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("personIdToKeep cannot be null", e.getMessage());
assertEquals("toPersonId cannot be null", e.getMessage());
}
}
@Test
public void testBadParams() {
try {
myEmpiProviderR4.mergePersons(new StringType("Patient/1"), new StringType("Patient/2"), myRequestDetails);
myEmpiProviderR4.mergePersons(new StringType("Patient/1"), new StringType("Patient/2"), myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("personIdToDelete must have form Person/<id> where <id> is the id of the person", e.getMessage());
assertEquals("fromPersonId must have form Person/<id> where <id> is the id of the person", e.getMessage());
}
try {
myEmpiProviderR4.mergePersons(myDeletePersonId, new StringType("Patient/2"), myRequestDetails);
myEmpiProviderR4.mergePersons(myFromPersonId, new StringType("Patient/2"), myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("personIdToKeep must have form Person/<id> where <id> is the id of the person", e.getMessage());
assertEquals("toPersonId must have form Person/<id> where <id> is the id of the person", e.getMessage());
}
try {
myEmpiProviderR4.mergePersons(new StringType("Person/1"), new StringType("Person/1"), myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("personIdToDelete must be different from personToKeep", e.getMessage());
assertEquals("fromPersonId must be different from toPersonId", e.getMessage());
}
try {
myEmpiProviderR4.mergePersons(new StringType("Person/abc"), myKeepPersonId, myRequestDetails);
myEmpiProviderR4.mergePersons(new StringType("Person/abc"), myToPersonId, myRequestDetails);
fail();
} catch (ResourceNotFoundException e) {
assertEquals("Resource Person/abc is not known", e.getMessage());
}
try {
myEmpiProviderR4.mergePersons(myDeletePersonId, new StringType("Person/abc"), myRequestDetails);
myEmpiProviderR4.mergePersons(myFromPersonId, new StringType("Person/abc"), myRequestDetails);
fail();
} catch (ResourceNotFoundException e) {
assertEquals("Resource Person/abc is not known", e.getMessage());

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.empi.provider;
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
import ca.uhn.fhir.jpa.entity.EmpiLink;
import ca.uhn.fhir.model.primitive.IdDt;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
@ -52,7 +53,7 @@ private static final Logger ourLog = LoggerFactory.getLogger(EmpiProviderQueryLi
List<Parameters.ParametersParameterComponent> list = result.getParameter();
assertThat(list, hasSize(1));
List<Parameters.ParametersParameterComponent> part = list.get(0).getPart();
assertEmpiLink(4, part, myPersonId.getValue(), myPatientId.getValue(), EmpiMatchResultEnum.MATCH);
assertEmpiLink(4, part, myPersonId.getValue(), myPatientId.getValue(), EmpiMatchResultEnum.POSSIBLE_MATCH);
}
@Test
@ -81,13 +82,12 @@ private static final Logger ourLog = LoggerFactory.getLogger(EmpiProviderQueryLi
assertEmpiLink(2, part, myPerson1Id.getValue(), myPerson2Id.getValue(), EmpiMatchResultEnum.POSSIBLE_DUPLICATE);
}
private void assertEmpiLink(int theExpectedSize, List<Parameters.ParametersParameterComponent> thePart, String thePersonId, String theTargetId, EmpiMatchResultEnum theMatchResult) {
assertThat(thePart, hasSize(theExpectedSize));
assertThat(thePart.get(0).getName(), is("personId"));
assertThat(thePart.get(0).getValue().toString(), is(thePersonId));
assertThat(thePart.get(0).getValue().toString(), is(removeVersion(thePersonId)));
assertThat(thePart.get(1).getName(), is("targetId"));
assertThat(thePart.get(1).getValue().toString(), is(theTargetId));
assertThat(thePart.get(1).getValue().toString(), is(removeVersion(theTargetId)));
if (theExpectedSize > 2) {
assertThat(thePart.get(2).getName(), is("matchResult"));
assertThat(thePart.get(2).getValue().toString(), is(theMatchResult.name()));
@ -95,4 +95,8 @@ private static final Logger ourLog = LoggerFactory.getLogger(EmpiProviderQueryLi
assertThat(thePart.get(3).getValue().toString(), is("AUTO"));
}
}
private String removeVersion(String theId) {
return new IdDt(theId).toVersionless().getValue();
}
}

View File

@ -3,33 +3,83 @@ package ca.uhn.fhir.jpa.empi.provider;
import ca.uhn.fhir.empi.api.EmpiConstants;
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
import ca.uhn.fhir.jpa.entity.EmpiLink;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Person;
import org.hl7.fhir.r4.model.StringType;
import org.junit.Test;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
public class EmpiProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testUpdateLinkNoMatch() {
assertLinkCount(1);
myEmpiProviderR4.updateLink(myPersonId, myPatientId, NO_MATCH_RESULT, myRequestDetails);
assertLinkCount(2);
List<EmpiLink> links = getPatientLinks();
assertEquals(EmpiLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
assertEquals(EmpiMatchResultEnum.NO_MATCH, links.get(0).getMatchResult());
assertEquals(EmpiLinkSourceEnum.AUTO, links.get(1).getLinkSource());
assertEquals(EmpiMatchResultEnum.MATCH, links.get(1).getMatchResult());
assertNotEquals(links.get(0).getPersonPid(), links.get(1).getPersonPid());
}
@Test
public void testUpdateLinkHappyPath() {
myEmpiProviderR4.updateLink(myPersonId, myPatientId, myNoMatch, myRequestDetails);
public void testUpdateLinkMatch() {
assertLinkCount(1);
myEmpiProviderR4.updateLink(myPersonId, myPatientId, MATCH_RESULT, myRequestDetails);
assertLinkCount(1);
myLink = getLink();
assertEquals(EmpiLinkSourceEnum.MANUAL, myLink.getLinkSource());
assertEquals(EmpiMatchResultEnum.NO_MATCH, myLink.getMatchResult());
List<EmpiLink> links = getPatientLinks();
assertEquals(EmpiLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
assertEquals(EmpiMatchResultEnum.MATCH, links.get(0).getMatchResult());
}
@Test
public void testUpdateLinkTwiceFailsDueToWrongVersion() {
myEmpiProviderR4.updateLink(myPersonId, myPatientId, MATCH_RESULT, myRequestDetails);
try {
myEmpiProviderR4.updateLink(myPersonId, myPatientId, NO_MATCH_RESULT, myRequestDetails);
fail();
} catch (ResourceVersionConflictException e) {
assertThat(e.getMessage(), matchesPattern("Requested resource Person/\\d+/_history/1 is not the latest version. Latest version is Person/\\d+/_history/2"));
}
}
@Test
public void testUpdateLinkTwiceWorksWhenNoVersionProvided() {
myEmpiProviderR4.updateLink(myPersonId, myPatientId, MATCH_RESULT, myRequestDetails);
Person person = myEmpiProviderR4.updateLink(myVersionlessPersonId, myPatientId, NO_MATCH_RESULT, myRequestDetails);
assertThat(person.getLink(), hasSize(0));
}
@Test
public void testUnlinkLink() {
myEmpiProviderR4.updateLink(myPersonId, myPatientId, NO_MATCH_RESULT, myRequestDetails);
try {
myEmpiProviderR4.updateLink(myPersonId, myPatientId, MATCH_RESULT, myRequestDetails);
fail();
} catch (ResourceVersionConflictException e) {
assertThat(e.getMessage(), matchesPattern("Requested resource Person/\\d+/_history/1 is not the latest version. Latest version is Person/\\d+/_history/2"));
}
}
@Test
public void testUpdateIllegalResultPM() {
try {
myEmpiProviderR4.updateLink(myPersonId, myPatientId, myPossibleMatch, myRequestDetails);
myEmpiProviderR4.updateLink(myPersonId, myPatientId, POSSIBLE_MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("$empi-update-link illegal matchResult value 'POSSIBLE_MATCH'. Must be NO_MATCH or MATCH", e.getMessage());
@ -39,7 +89,7 @@ public class EmpiProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testUpdateIllegalResultPD() {
try {
myEmpiProviderR4.updateLink(myPersonId, myPatientId, myPossibleDuplicate, myRequestDetails);
myEmpiProviderR4.updateLink(myPersonId, myPatientId, POSSIBLE_DUPLICATE_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("$empi-update-link illegal matchResult value 'POSSIBLE_DUPLICATE'. Must be NO_MATCH or MATCH", e.getMessage());
@ -49,7 +99,7 @@ public class EmpiProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testUpdateIllegalFirstArg() {
try {
myEmpiProviderR4.updateLink(myPatientId, myPatientId, myNoMatch, myRequestDetails);
myEmpiProviderR4.updateLink(myPatientId, myPatientId, NO_MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("personId must have form Person/<id> where <id> is the id of the person", e.getMessage());
@ -59,7 +109,7 @@ public class EmpiProviderUpdateLinkR4Test extends BaseLinkR4Test {
@Test
public void testUpdateIllegalSecondArg() {
try {
myEmpiProviderR4.updateLink(myPersonId, myPersonId, myNoMatch, myRequestDetails);
myEmpiProviderR4.updateLink(myPersonId, myPersonId, NO_MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), endsWith("must have form Patient/<id> or Practitioner/<id> where <id> is the id of the resource"));
@ -70,7 +120,7 @@ public class EmpiProviderUpdateLinkR4Test extends BaseLinkR4Test {
public void testUpdateStrangePerson() {
Person person = createUnmanagedPerson();
try {
myEmpiProviderR4.updateLink(new StringType(person.getIdElement().toVersionless().getValue()), myPatientId, myNoMatch, myRequestDetails);
myEmpiProviderR4.updateLink(new StringType(person.getIdElement().getValue()), myPatientId, NO_MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("Only EMPI Managed Person resources may be updated via this operation. The Person resource provided is not tagged as managed by hapi-empi", e.getMessage());
@ -83,7 +133,7 @@ public class EmpiProviderUpdateLinkR4Test extends BaseLinkR4Test {
patient.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_NO_EMPI_MANAGED);
createPatient(patient);
try {
myEmpiProviderR4.updateLink(myPersonId, new StringType(patient.getIdElement().toVersionless().getValue()), myNoMatch, myRequestDetails);
myEmpiProviderR4.updateLink(myPersonId, new StringType(patient.getIdElement().getValue()), NO_MATCH_RESULT, myRequestDetails);
fail();
} catch (InvalidRequestException e) {
assertEquals("The target is marked with the " + EmpiConstants.CODE_NO_EMPI_MANAGED + " tag which means it may not be EMPI linked.", e.getMessage());

View File

@ -1,9 +1,7 @@
package ca.uhn.fhir.jpa.empi.searchparam;
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
import ca.uhn.fhir.jpa.empi.config.EmpiSearchParameterLoader;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.TokenParam;
import org.hl7.fhir.r4.model.Patient;
@ -12,7 +10,6 @@ import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@ -22,15 +19,10 @@ import static org.junit.Assert.assertThat;
public class SearchParameterTest extends BaseEmpiR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(SearchParameterTest.class);
@Autowired
EmpiSearchParameterLoader myEmpiSearchParameterLoader;
@Autowired
SearchParamRegistryImpl mySearchParamRegistry;
@Before
public void before() {
myEmpiSearchParameterLoader.daoUpdateEmpiSearchParameters();
mySearchParamRegistry.forceRefresh();
super.loadEmpiSearchParameters();
}
@Test

View File

@ -0,0 +1,10 @@
package ca.uhn.fhir.jpa.empi.svc;
import ca.uhn.fhir.jpa.empi.provider.EmpiProviderUpdateLinkR4Test;
/**
* Tests for this service are in the test for the provider that wraps this service:
* @see EmpiProviderUpdateLinkR4Test
*/
public class EmpiLinkUpdaterSvcImplTest {
}

View File

@ -49,24 +49,24 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
@Autowired
IInterceptorService myInterceptorService;
private Person myDeletePerson;
private Person myKeepPerson;
private IdType myDeletePersonId;
private IdType myKeepPersonId;
private Long myDeletePersonPid;
private Long myKeepPersonPid;
private Person myFromPerson;
private Person myToPerson;
private Long myFromPersonPid;
private Long myToPersonPid;
private Patient myTargetPatient1;
private Patient myTargetPatient2;
private Patient myTargetPatient3;
@Before
public void before() {
myDeletePerson = createPerson();
myDeletePersonId = myDeletePerson.getIdElement().toUnqualifiedVersionless();
myDeletePersonPid = myIdHelperService.getPidOrThrowException(myDeletePersonId);
myKeepPerson = createPerson();
myKeepPersonId = myKeepPerson.getIdElement().toUnqualifiedVersionless();
myKeepPersonPid = myIdHelperService.getPidOrThrowException(myKeepPersonId);
super.loadEmpiSearchParameters();
myFromPerson = createPerson();
IdType fromPersonId = myFromPerson.getIdElement().toUnqualifiedVersionless();
myFromPersonPid = myIdHelperService.getPidOrThrowException(fromPersonId);
myToPerson = createPerson();
IdType toPersonId = myToPerson.getIdElement().toUnqualifiedVersionless();
myToPersonPid = myIdHelperService.getPidOrThrowException(toPersonId);
myTargetPatient1 = createPatient();
@ -87,15 +87,17 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
@Test
public void emptyMerge() {
assertEquals(2, getAllPersons().size());
assertEquals(2, getAllActivePersons().size());
Person mergedPerson = mergePersons();
assertEquals(myKeepPerson.getIdElement(), mergedPerson.getIdElement());
assertEquals(myToPerson.getIdElement(), mergedPerson.getIdElement());
assertThat(mergedPerson, is(samePersonAs(mergedPerson)));
assertEquals(1, getAllPersons().size());
assertEquals(2, getAllPersons().size());
assertEquals(1, getAllActivePersons().size());
}
private Person mergePersons() {
return (Person) myEmpiPersonMergerSvc.mergePersons(myDeletePerson, myKeepPerson, createEmpiContext());
return (Person) myEmpiPersonMergerSvc.mergePersons(myFromPerson, myToPerson, createEmpiContext());
}
private EmpiTransactionContext createEmpiContext() {
@ -104,7 +106,7 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
@Test
public void mergeRemovesPossibleDuplicatesLink() {
EmpiLink empiLink = new EmpiLink().setPersonPid(myKeepPersonPid).setTargetPid(myDeletePersonPid).setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE).setLinkSource(EmpiLinkSourceEnum.AUTO);
EmpiLink empiLink = new EmpiLink().setPersonPid(myToPersonPid).setTargetPid(myFromPersonPid).setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE).setLinkSource(EmpiLinkSourceEnum.AUTO);
myEmpiLinkDaoSvc.save(empiLink);
assertEquals(1, myEmpiLinkDao.count());
mergePersons();
@ -112,8 +114,8 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
}
@Test
public void fullDeleteEmptyKeep() {
populatePerson(myDeletePerson);
public void fullFromEmptyTo() {
populatePerson(myFromPerson);
Person mergedPerson = mergePersons();
HumanName returnedName = mergedPerson.getNameFirstRep();
@ -123,9 +125,9 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
}
@Test
public void emptyDeleteFullKeep() {
myDeletePerson.getName().add(new HumanName().addGiven(BAD_GIVEN_NAME));
populatePerson(myKeepPerson);
public void emptyFromFullTo() {
myFromPerson.getName().add(new HumanName().addGiven(BAD_GIVEN_NAME));
populatePerson(myToPerson);
Person mergedPerson = mergePersons();
HumanName returnedName = mergedPerson.getNameFirstRep();
@ -135,83 +137,83 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
}
@Test
public void deleteLinkKeepNoLink() {
createEmpiLink(myDeletePerson, myTargetPatient1);
public void fromLinkToNoLink() {
createEmpiLink(myFromPerson, myTargetPatient1);
mergePersons();
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myKeepPerson);
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myToPerson);
assertEquals(1, links.size());
assertThat(myKeepPerson, is(possibleLinkedTo(myTargetPatient1)));
assertEquals(1, myKeepPerson.getLink().size());
assertThat(myToPerson, is(possibleLinkedTo(myTargetPatient1)));
assertEquals(1, myToPerson.getLink().size());
}
@Test
public void deleteNoLinkKeepLink() {
createEmpiLink(myKeepPerson, myTargetPatient1);
public void fromNoLinkToLink() {
createEmpiLink(myToPerson, myTargetPatient1);
mergePersons();
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myKeepPerson);
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myToPerson);
assertEquals(1, links.size());
assertThat(myKeepPerson, is(possibleLinkedTo(myTargetPatient1)));
assertEquals(1, myKeepPerson.getLink().size());
assertThat(myToPerson, is(possibleLinkedTo(myTargetPatient1)));
assertEquals(1, myToPerson.getLink().size());
}
@Test
public void deleteManualLinkOverridesAutoKeepLink() {
EmpiLink deleteLink = createEmpiLink(myDeletePerson, myTargetPatient1);
deleteLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
deleteLink.setMatchResult(EmpiMatchResultEnum.MATCH);
myEmpiLinkDaoSvc.save(deleteLink);
public void fromManualLinkOverridesAutoToLink() {
EmpiLink fromLink = createEmpiLink(myFromPerson, myTargetPatient1);
fromLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
fromLink.setMatchResult(EmpiMatchResultEnum.MATCH);
myEmpiLinkDaoSvc.save(fromLink);
createEmpiLink(myKeepPerson, myTargetPatient1);
createEmpiLink(myToPerson, myTargetPatient1);
mergePersons();
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myKeepPerson);
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myToPerson);
assertEquals(1, links.size());
assertEquals(EmpiLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
}
@Test
public void deleteManualNoMatchLinkOverridesAutoKeepLink() {
EmpiLink deleteLink = createEmpiLink(myDeletePerson, myTargetPatient1);
deleteLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
deleteLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH);
myEmpiLinkDaoSvc.save(deleteLink);
public void fromManualNoMatchLinkOverridesAutoToLink() {
EmpiLink fromLink = createEmpiLink(myFromPerson, myTargetPatient1);
fromLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
fromLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH);
myEmpiLinkDaoSvc.save(fromLink);
createEmpiLink(myKeepPerson, myTargetPatient1);
createEmpiLink(myToPerson, myTargetPatient1);
mergePersons();
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myKeepPerson);
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myToPerson);
assertEquals(1, links.size());
assertEquals(EmpiLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
}
@Test
public void deleteManualAutoMatchLinkNoOverridesManualKeepLink() {
createEmpiLink(myDeletePerson, myTargetPatient1);
public void fromManualAutoMatchLinkNoOverridesManualToLink() {
createEmpiLink(myFromPerson, myTargetPatient1);
EmpiLink keepLink = createEmpiLink(myKeepPerson, myTargetPatient1);
keepLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
keepLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH);
myEmpiLinkDaoSvc.save(keepLink);
EmpiLink toLink = createEmpiLink(myToPerson, myTargetPatient1);
toLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
toLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH);
myEmpiLinkDaoSvc.save(toLink);
mergePersons();
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myKeepPerson);
List<EmpiLink> links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myToPerson);
assertEquals(1, links.size());
assertEquals(EmpiLinkSourceEnum.MANUAL, links.get(0).getLinkSource());
}
@Test
public void deleteNoMatchMergeToManualMatchIsError() {
EmpiLink deleteLink = createEmpiLink(myDeletePerson, myTargetPatient1);
deleteLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
deleteLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH);
myEmpiLinkDaoSvc.save(deleteLink);
public void fromNoMatchMergeToManualMatchIsError() {
EmpiLink fromLink = createEmpiLink(myFromPerson, myTargetPatient1);
fromLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
fromLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH);
myEmpiLinkDaoSvc.save(fromLink);
EmpiLink keepLink = createEmpiLink(myKeepPerson, myTargetPatient1);
keepLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
keepLink.setMatchResult(EmpiMatchResultEnum.MATCH);
myEmpiLinkDaoSvc.save(keepLink);
EmpiLink toLink = createEmpiLink(myToPerson, myTargetPatient1);
toLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
toLink.setMatchResult(EmpiMatchResultEnum.MATCH);
myEmpiLinkDaoSvc.save(toLink);
try {
mergePersons();
@ -222,16 +224,16 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
}
@Test
public void deleteMatchMergeToManualNoMatchIsError() {
EmpiLink deleteLink = createEmpiLink(myDeletePerson, myTargetPatient1);
deleteLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
deleteLink.setMatchResult(EmpiMatchResultEnum.MATCH);
myEmpiLinkDaoSvc.save(deleteLink);
public void fromMatchMergeToManualNoMatchIsError() {
EmpiLink fromLink = createEmpiLink(myFromPerson, myTargetPatient1);
fromLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
fromLink.setMatchResult(EmpiMatchResultEnum.MATCH);
myEmpiLinkDaoSvc.save(fromLink);
EmpiLink keepLink = createEmpiLink(myKeepPerson, myTargetPatient1);
keepLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
keepLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH);
myEmpiLinkDaoSvc.save(keepLink);
EmpiLink toLink = createEmpiLink(myToPerson, myTargetPatient1);
toLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
toLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH);
myEmpiLinkDaoSvc.save(toLink);
try {
mergePersons();
@ -242,127 +244,127 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
}
@Test
public void deleteNoMatchMergeToManualMatchDifferentPatientIsOk() {
EmpiLink deleteLink = createEmpiLink(myDeletePerson, myTargetPatient1);
deleteLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
deleteLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH);
myEmpiLinkDaoSvc.save(deleteLink);
public void fromNoMatchMergeToManualMatchDifferentPatientIsOk() {
EmpiLink fromLink = createEmpiLink(myFromPerson, myTargetPatient1);
fromLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
fromLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH);
myEmpiLinkDaoSvc.save(fromLink);
EmpiLink keepLink = createEmpiLink(myKeepPerson, myTargetPatient2);
keepLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
keepLink.setMatchResult(EmpiMatchResultEnum.MATCH);
myEmpiLinkDaoSvc.save(keepLink);
EmpiLink toLink = createEmpiLink(myToPerson, myTargetPatient2);
toLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
toLink.setMatchResult(EmpiMatchResultEnum.MATCH);
myEmpiLinkDaoSvc.save(toLink);
mergePersons();
assertEquals(1, myKeepPerson.getLink().size());
assertEquals(1, myToPerson.getLink().size());
assertEquals(2, myEmpiLinkDao.count());
}
@Test
public void delete123Keep1() {
createEmpiLink(myDeletePerson, myTargetPatient1);
createEmpiLink(myDeletePerson, myTargetPatient2);
createEmpiLink(myDeletePerson, myTargetPatient3);
createEmpiLink(myKeepPerson, myTargetPatient1);
public void from123To1() {
createEmpiLink(myFromPerson, myTargetPatient1);
createEmpiLink(myFromPerson, myTargetPatient2);
createEmpiLink(myFromPerson, myTargetPatient3);
createEmpiLink(myToPerson, myTargetPatient1);
mergePersons();
myEmpiLinkHelper.logEmpiLinks();
assertThat(myKeepPerson, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3)));
assertEquals(3, myKeepPerson.getLink().size());
assertThat(myToPerson, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3)));
assertEquals(3, myToPerson.getLink().size());
}
@Test
public void delete1Keep123() {
createEmpiLink(myDeletePerson, myTargetPatient1);
createEmpiLink(myKeepPerson, myTargetPatient1);
createEmpiLink(myKeepPerson, myTargetPatient2);
createEmpiLink(myKeepPerson, myTargetPatient3);
public void from1To123() {
createEmpiLink(myFromPerson, myTargetPatient1);
createEmpiLink(myToPerson, myTargetPatient1);
createEmpiLink(myToPerson, myTargetPatient2);
createEmpiLink(myToPerson, myTargetPatient3);
mergePersons();
myEmpiLinkHelper.logEmpiLinks();
assertThat(myKeepPerson, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3)));
assertEquals(3, myKeepPerson.getLink().size());
assertThat(myToPerson, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3)));
assertEquals(3, myToPerson.getLink().size());
}
@Test
public void delete123Keep123() {
createEmpiLink(myDeletePerson, myTargetPatient1);
createEmpiLink(myDeletePerson, myTargetPatient2);
createEmpiLink(myDeletePerson, myTargetPatient3);
createEmpiLink(myKeepPerson, myTargetPatient1);
createEmpiLink(myKeepPerson, myTargetPatient2);
createEmpiLink(myKeepPerson, myTargetPatient3);
public void from123To123() {
createEmpiLink(myFromPerson, myTargetPatient1);
createEmpiLink(myFromPerson, myTargetPatient2);
createEmpiLink(myFromPerson, myTargetPatient3);
createEmpiLink(myToPerson, myTargetPatient1);
createEmpiLink(myToPerson, myTargetPatient2);
createEmpiLink(myToPerson, myTargetPatient3);
mergePersons();
myEmpiLinkHelper.logEmpiLinks();
assertThat(myKeepPerson, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3)));
assertEquals(3, myKeepPerson.getLink().size());
assertThat(myToPerson, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3)));
assertEquals(3, myToPerson.getLink().size());
}
@Test
public void delete12Keep23() {
createEmpiLink(myDeletePerson, myTargetPatient1);
createEmpiLink(myDeletePerson, myTargetPatient2);
createEmpiLink(myKeepPerson, myTargetPatient2);
createEmpiLink(myKeepPerson, myTargetPatient3);
public void from12To23() {
createEmpiLink(myFromPerson, myTargetPatient1);
createEmpiLink(myFromPerson, myTargetPatient2);
createEmpiLink(myToPerson, myTargetPatient2);
createEmpiLink(myToPerson, myTargetPatient3);
mergePersons();
myEmpiLinkHelper.logEmpiLinks();
assertThat(myKeepPerson, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3)));
assertEquals(3, myKeepPerson.getLink().size());
assertThat(myToPerson, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3)));
assertEquals(3, myToPerson.getLink().size());
}
@Test
public void testMergeNames() {
myDeletePerson.addName().addGiven("Jim");
myDeletePerson.getNameFirstRep().addGiven("George");
assertThat(myDeletePerson.getName(), hasSize(1));
assertThat(myDeletePerson.getName().get(0).getGiven(), hasSize(2));
myFromPerson.addName().addGiven("Jim");
myFromPerson.getNameFirstRep().addGiven("George");
assertThat(myFromPerson.getName(), hasSize(1));
assertThat(myFromPerson.getName().get(0).getGiven(), hasSize(2));
myKeepPerson.addName().addGiven("Jeff");
myKeepPerson.getNameFirstRep().addGiven("George");
assertThat(myKeepPerson.getName(), hasSize(1));
assertThat(myKeepPerson.getName().get(0).getGiven(), hasSize(2));
myToPerson.addName().addGiven("Jeff");
myToPerson.getNameFirstRep().addGiven("George");
assertThat(myToPerson.getName(), hasSize(1));
assertThat(myToPerson.getName().get(0).getGiven(), hasSize(2));
mergePersons();
assertThat(myKeepPerson.getName(), hasSize(2));
assertThat(myKeepPerson.getName().get(0).getGiven(), hasSize(2));
assertThat(myKeepPerson.getName().get(1).getGiven(), hasSize(2));
assertThat(myToPerson.getName(), hasSize(2));
assertThat(myToPerson.getName().get(0).getGiven(), hasSize(2));
assertThat(myToPerson.getName().get(1).getGiven(), hasSize(2));
}
@Test
public void testMergeNamesAllSame() {
myDeletePerson.addName().addGiven("Jim");
myDeletePerson.getNameFirstRep().addGiven("George");
assertThat(myDeletePerson.getName(), hasSize(1));
assertThat(myDeletePerson.getName().get(0).getGiven(), hasSize(2));
myFromPerson.addName().addGiven("Jim");
myFromPerson.getNameFirstRep().addGiven("George");
assertThat(myFromPerson.getName(), hasSize(1));
assertThat(myFromPerson.getName().get(0).getGiven(), hasSize(2));
myKeepPerson.addName().addGiven("Jim");
myKeepPerson.getNameFirstRep().addGiven("George");
assertThat(myKeepPerson.getName(), hasSize(1));
assertThat(myKeepPerson.getName().get(0).getGiven(), hasSize(2));
myToPerson.addName().addGiven("Jim");
myToPerson.getNameFirstRep().addGiven("George");
assertThat(myToPerson.getName(), hasSize(1));
assertThat(myToPerson.getName().get(0).getGiven(), hasSize(2));
mergePersons();
assertThat(myKeepPerson.getName(), hasSize(1));
assertThat(myKeepPerson.getName().get(0).getGiven(), hasSize(2));
assertThat(myToPerson.getName(), hasSize(1));
assertThat(myToPerson.getName().get(0).getGiven(), hasSize(2));
}
@Test
public void testMergeIdentities() {
myDeletePerson.addIdentifier().setValue("aaa");
myDeletePerson.addIdentifier().setValue("bbb");
assertThat(myDeletePerson.getIdentifier(), hasSize(2));
myFromPerson.addIdentifier().setValue("aaa");
myFromPerson.addIdentifier().setValue("bbb");
assertThat(myFromPerson.getIdentifier(), hasSize(2));
myKeepPerson.addIdentifier().setValue("aaa");
myKeepPerson.addIdentifier().setValue("ccc");
assertThat(myKeepPerson.getIdentifier(), hasSize(2));
myToPerson.addIdentifier().setValue("aaa");
myToPerson.addIdentifier().setValue("ccc");
assertThat(myToPerson.getIdentifier(), hasSize(2));
mergePersons();
assertThat(myKeepPerson.getIdentifier(), hasSize(3));
assertThat(myToPerson.getIdentifier(), hasSize(3));
}
private EmpiLink createEmpiLink(Person thePerson, Patient theTargetPatient) {

View File

@ -1,51 +0,0 @@
package ca.uhn.fhir.jpa.model.entity;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.IndexedEmbedded;
import javax.persistence.*;
import java.util.Set;
@Embeddable
public class ObservationIndexedCategoryCodeableConceptEntity {
@Field(name = "text")
private String myCodeableConceptText;
@IndexedEmbedded(depth=2, prefix = "coding")
@OneToMany(mappedBy = "myCodeableConceptId", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<ObservationIndexedCategoryCodingEntity> myObservationIndexedCategoryCodingEntitySet;
public ObservationIndexedCategoryCodeableConceptEntity(String theCodeableConceptText) {
setCodeableConceptText(theCodeableConceptText);
}
public void setObservationIndexedCategoryCodingEntitySet(Set<ObservationIndexedCategoryCodingEntity> theObservationIndexedCategoryCodingEntitySet) {
myObservationIndexedCategoryCodingEntitySet = theObservationIndexedCategoryCodingEntitySet;
}
public void setCodeableConceptText(String theCodeableConceptText) {
myCodeableConceptText = theCodeableConceptText;
}
}

View File

@ -1,48 +0,0 @@
package ca.uhn.fhir.jpa.model.entity;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Field;
import javax.persistence.*;
@Embeddable
public class ObservationIndexedCategoryCodingEntity {
@Field (name = "code", analyze = Analyze.NO)
private String myCode;
@Field (name = "system", analyze = Analyze.NO)
private String mySystem;
@Field (name = "code_system_hash", analyze = Analyze.NO)
private String myCodeSystemHash;
@Field (name = "display")
private String myDisplay;
public ObservationIndexedCategoryCodingEntity(String theSystem, String theCode, String theDisplay) {
myCode = theCode;
mySystem = theSystem;
myCodeSystemHash = String.valueOf(CodeSystemHash.hashCodeSystem(theSystem, theCode));
myDisplay = theDisplay;
}
}

View File

@ -1,81 +0,0 @@
package ca.uhn.fhir.jpa.model.entity;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import javax.persistence.*;
@Entity
@Indexed(index = "code_index")
@Embeddable
@Table(name = "HFJ_SPIDX_LASTN_CODE_CONCEPT")
public class ObservationIndexedCodeCodeableConceptEntity {
public static final int MAX_LENGTH = 200;
@Id
@DocumentId(name = "codeable_concept_id")
@Column(name="CODEABLE_CONCEPT_ID", length = MAX_LENGTH)
private String myCodeableConceptId;
@Field(name = "text")
@Column(name = "CODEABLE_CONCEPT_TEXT", nullable = true, length = MAX_LENGTH)
private String myCodeableConceptText;
@IndexedEmbedded(depth=2, prefix = "coding")
@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 ObservationIndexedCodeCodingEntity myObservationIndexedCodeCodingEntity;
public ObservationIndexedCodeCodeableConceptEntity() {
}
public ObservationIndexedCodeCodeableConceptEntity(String theCodeableConceptText, String theCodeableConceptId) {
setCodeableConceptText(theCodeableConceptText);
setCodeableConceptId(theCodeableConceptId);
}
public void addCoding(ObservationIndexedCodeCodingEntity theObservationIndexedCodeCodingEntity) {
myObservationIndexedCodeCodingEntity = theObservationIndexedCodeCodingEntity;
}
public String getCodeableConceptId() {
return myCodeableConceptId;
}
public void setCodeableConceptId(String theCodeableConceptId) {
myCodeableConceptId = theCodeableConceptId;
}
public String getCodeableConceptText() {
return myCodeableConceptText;
}
public void setCodeableConceptText(String theCodeableConceptText) {
myCodeableConceptText = theCodeableConceptText;
}
}

View File

@ -1,63 +0,0 @@
package ca.uhn.fhir.jpa.model.entity;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.model.util.CodeSystemHash;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Field;
import javax.persistence.*;
@Entity
@Embeddable
@Table(name = "HFJ_SPIDX_LASTN_CODING")
public class ObservationIndexedCodeCodingEntity {
public static final int MAX_LENGTH = 200;
@Id
@Column(name = "CODEABLE_CONCEPT_ID", length = MAX_LENGTH)
private String myCodeableConceptId;
@Field(name = "code", analyze = Analyze.NO)
private String myCode;
@Field(name = "system", analyze = Analyze.NO)
private String mySystem;
@Field(name = "code_system_hash", analyze = Analyze.NO)
private String myCodeSystemHash;
@Field(name = "display")
private String myDisplay;
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;
}
}

View File

@ -1,114 +0,0 @@
package ca.uhn.fhir.jpa.model.entity;
/*-
* #%L
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hibernate.search.annotations.*;
import javax.persistence.*;
import javax.persistence.Index;
import java.util.*;
@Entity
@Table(name = "HFJ_LASTN_OBSERVATION", indexes = {
@Index(name = "IDX_LASTN_OBSERVATION_RESID", columnList = "RESOURCE_IDENTIFIER", unique = true)
})
@Indexed(index = "observation_index")
public class ObservationIndexedSearchParamLastNEntity {
public static final int MAX_LENGTH = 200;
@Id
@SequenceGenerator(name = "SEQ_LASTN", sequenceName = "SEQ_LASTN")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_LASTN")
@Column(name = "LASTN_ID")
private Long myId;
@Field(name = "subject", analyze = Analyze.NO)
@Column(name = "LASTN_SUBJECT_ID", nullable = true, length = MAX_LENGTH)
private String mySubject;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CODEABLE_CONCEPT_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_OBSERVATION_CODE_FK"))
@IndexedEmbedded(depth = 2, prefix = "codeconcept")
private ObservationIndexedCodeCodeableConceptEntity myObservationCode;
@Field(name = "codeconceptid", analyze = Analyze.NO)
@Column(name = "CODEABLE_CONCEPT_ID", nullable = false, updatable = false, insertable = false, length = MAX_LENGTH)
private String myCodeNormalizedId;
@IndexedEmbedded(depth = 2, prefix = "categoryconcept")
@Transient
private Set<ObservationIndexedCategoryCodeableConceptEntity> myCategoryCodeableConcepts;
@Field(name = "effectivedtm", analyze = Analyze.NO)
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "LASTN_EFFECTIVE_DATETIME", nullable = true)
private Date myEffectiveDtm;
@DocumentId(name = "identifier")
@Column(name = "RESOURCE_IDENTIFIER", nullable = false, length = MAX_LENGTH)
private String myIdentifier;
public ObservationIndexedSearchParamLastNEntity() {
}
public String getSubject() {
return mySubject;
}
public void setSubject(String theSubject) {
mySubject = theSubject;
}
public String getIdentifier() {
return myIdentifier;
}
public void setIdentifier(String theIdentifier) {
myIdentifier = theIdentifier;
}
public void setEffectiveDtm(Date theEffectiveDtm) {
myEffectiveDtm = theEffectiveDtm;
}
public Date getEffectiveDtm() {
return myEffectiveDtm;
}
public void setCodeNormalizedId(String theCodeNormalizedId) {
myCodeNormalizedId = theCodeNormalizedId;
}
public String getCodeNormalizedId() {
return myCodeNormalizedId;
}
public void setObservationCode(ObservationIndexedCodeCodeableConceptEntity theObservationCode) {
myObservationCode = theObservationCode;
}
public void setCategoryCodeableConcepts(Set<ObservationIndexedCategoryCodeableConceptEntity> theCategoryCodeableConcepts) {
myCategoryCodeableConcepts = theCategoryCodeableConcepts;
}
}

View File

@ -28,6 +28,7 @@ import org.springframework.test.context.junit4.SpringRunner;
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Date;
import static org.junit.Assert.assertEquals;
@ -208,6 +209,68 @@ public class InMemoryResourceMatcherR5Test {
assertTrue(result.matched());
}
@Test
public void testTodayPast() {
InMemoryMatchResult result = myInMemoryResourceMatcher.match("date=lt" + BaseDateTimeDt.TODAY_DATE_CONSTANT, myObservation, mySearchParams);
assertTrue(result.getUnsupportedReason(), result.supported());
assertTrue(result.matched());
}
@Test
public void testTodayNextWeek() {
Observation futureObservation = new Observation();
Instant nextWeek = Instant.now().plus(Duration.ofDays(7));
futureObservation.setEffective(new DateTimeType(Date.from(nextWeek)));
ResourceIndexedSearchParams searchParams = extractDateSearchParam(futureObservation);
InMemoryMatchResult result = myInMemoryResourceMatcher.match("date=gt" + BaseDateTimeDt.TODAY_DATE_CONSTANT, futureObservation, searchParams);
assertTrue(result.getUnsupportedReason(), result.supported());
assertTrue(result.matched());
}
@Test
public void testTodayTomorrow() {
Observation futureObservation = new Observation();
Instant nextWeek = Instant.now().plus(Duration.ofDays(1));
futureObservation.setEffective(new DateTimeType(Date.from(nextWeek)));
ResourceIndexedSearchParams searchParams = extractDateSearchParam(futureObservation);
InMemoryMatchResult result = myInMemoryResourceMatcher.match("date=gt" + BaseDateTimeDt.TODAY_DATE_CONSTANT, futureObservation, searchParams);
assertTrue(result.getUnsupportedReason(), result.supported());
assertTrue(result.matched());
}
@Test
public void testTodayYesterday() {
Observation futureObservation = new Observation();
Instant nextWeek = Instant.now().minus(Duration.ofDays(1));
futureObservation.setEffective(new DateTimeType(Date.from(nextWeek)));
ResourceIndexedSearchParams searchParams = extractDateSearchParam(futureObservation);
InMemoryMatchResult result = myInMemoryResourceMatcher.match("date=gt" + BaseDateTimeDt.TODAY_DATE_CONSTANT, futureObservation, searchParams);
assertTrue(result.getUnsupportedReason(), result.supported());
assertFalse(result.matched());
}
@Test
public void testTodayNextMinute() {
Observation futureObservation = new Observation();
ZonedDateTime now = ZonedDateTime.now();
if (now.getHour() == 23 && now.getMinute() == 59) {
// this test fails between 23:59 and midnight...
return;
}
Instant nextMinute = now.toInstant().plus(Duration.ofMinutes(1));
futureObservation.setEffective(new DateTimeType(Date.from(nextMinute)));
ResourceIndexedSearchParams searchParams = extractDateSearchParam(futureObservation);
InMemoryMatchResult result = myInMemoryResourceMatcher.match("date=gt" + BaseDateTimeDt.TODAY_DATE_CONSTANT, futureObservation, searchParams);
assertTrue(result.getUnsupportedReason(), result.supported());
assertFalse(result.matched());
}
private ResourceIndexedSearchParams extractDateSearchParam(Observation theObservation) {
ResourceIndexedSearchParams retval = new ResourceIndexedSearchParams();
BaseDateTimeType dateValue = (BaseDateTimeType) theObservation.getEffective();

View File

@ -25,11 +25,11 @@ import org.hl7.fhir.instance.model.api.IAnyResource;
public interface IEmpiPersonMergerSvc {
/**
* Move all links from the thePersonToDelete to thePersonToKeep and then delete thePersonToDelete. Merge all Person
* fields, with fields in thePersonToKeep overriding fields in thePersonToDelete
* @param thePersonToDelete the person we are merging from
* @param thePersonToKeep the person we are merging to
* @return updated thePersonToKeep with the merged fields and links.
* Move all links from the theFromPerson to theToPerson and then set active=false on theFromPerson. Merge all Person
* fields.
* @param theFromPerson the person we are merging from
* @param theToPerson the person we are merging to
* @return updated theToPerson with the merged fields and links.
*/
IAnyResource mergePersons(IAnyResource thePersonToDelete, IAnyResource thePersonToKeep, EmpiTransactionContext theEmpiTransactionContext);
IAnyResource mergePersons(IAnyResource theFromPerson, IAnyResource theToPerson, EmpiTransactionContext theEmpiTransactionContext);
}

View File

@ -30,6 +30,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.TransactionLogMessages;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import ca.uhn.fhir.validation.IResourceLoader;
import org.hl7.fhir.instance.model.api.IAnyResource;
@ -47,9 +48,9 @@ public abstract class BaseEmpiProvider {
myResourceLoader = theResourceLoader;
}
protected IAnyResource getPersonFromIdOrThrowException(String theParamName, String theId) {
protected IAnyResource getLatestPersonFromIdOrThrowException(String theParamName, String theId) {
IdDt personId = getPersonIdDtOrThrowException(theParamName, theId);
return loadResource(personId);
return loadResource(personId.toUnqualifiedVersionless());
}
private IdDt getPersonIdDtOrThrowException(String theParamName, String theId) {
@ -61,9 +62,9 @@ public abstract class BaseEmpiProvider {
return personId;
}
protected IAnyResource getTargetFromIdOrThrowException(String theParamName, String theId) {
protected IAnyResource getLatestTargetFromIdOrThrowException(String theParamName, String theId) {
IIdType targetId = getTargetIdDtOrThrowException(theParamName, theId);
return loadResource(targetId);
return loadResource(targetId.toUnqualifiedVersionless());
}
protected IIdType getTargetIdDtOrThrowException(String theParamName, String theId) {
@ -81,17 +82,17 @@ public abstract class BaseEmpiProvider {
return (IAnyResource) myResourceLoader.load(resourceClass, theResourceId);
}
protected void validateMergeParameters(IPrimitiveType<String> thePersonIdToDelete, IPrimitiveType<String> thePersonIdToKeep) {
validateNotNull(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE, thePersonIdToDelete);
validateNotNull(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP, thePersonIdToKeep);
if (thePersonIdToDelete.getValue().equals(thePersonIdToKeep.getValue())) {
throw new InvalidRequestException("personIdToDelete must be different from personToKeep");
protected void validateMergeParameters(IPrimitiveType<String> theFromPersonId, IPrimitiveType<String> theToPersonId) {
validateNotNull(ProviderConstants.EMPI_MERGE_PERSONS_FROM_PERSON_ID, theFromPersonId);
validateNotNull(ProviderConstants.EMPI_MERGE_PERSONS_TO_PERSON_ID, theToPersonId);
if (theFromPersonId.getValue().equals(theToPersonId.getValue())) {
throw new InvalidRequestException("fromPersonId must be different from toPersonId");
}
}
protected void validateMergeResources(IAnyResource thePersonToDelete, IAnyResource thePersonToKeep) {
validateIsEmpiManaged(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE, thePersonToDelete);
validateIsEmpiManaged(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP, thePersonToKeep);
protected void validateMergeResources(IAnyResource theFromPerson, IAnyResource theToPerson) {
validateIsEmpiManaged(ProviderConstants.EMPI_MERGE_PERSONS_FROM_PERSON_ID, theFromPerson);
validateIsEmpiManaged(ProviderConstants.EMPI_MERGE_PERSONS_TO_PERSON_ID, theToPerson);
}
private void validateIsEmpiManaged(String theName, IAnyResource thePerson) {
@ -168,4 +169,15 @@ public abstract class BaseEmpiProvider {
return getTargetIdDtOrThrowException(theName, targetId);
}
protected void validateSameVersion(IAnyResource theResource, IPrimitiveType<String> theResourceId) {
String storedId = theResource.getIdElement().getValue();
String requestedId = theResourceId.getValue();
if (hasVersionIdPart(requestedId) && !storedId.equals(requestedId)) {
throw new ResourceVersionConflictException("Requested resource " + requestedId + " is not the latest version. Latest version is " + storedId);
}
}
private boolean hasVersionIdPart(String theId) {
return new IdDt(theId).hasVersionIdPart();
}
}

View File

@ -87,15 +87,17 @@ public class EmpiProviderDstu3 extends BaseEmpiProvider {
}
@Operation(name = ProviderConstants.EMPI_MERGE_PERSONS, type = Person.class)
public Person mergePerson(@OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE, min = 1, max = 1) StringType thePersonIdToDelete,
@OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP, min = 1, max = 1) StringType thePersonIdToKeep,
public Person mergePerson(@OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_FROM_PERSON_ID, min = 1, max = 1) StringType theFromPersonId,
@OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_TO_PERSON_ID, min = 1, max = 1) StringType theToPersonId,
RequestDetails theRequestDetails) {
validateMergeParameters(thePersonIdToDelete, thePersonIdToKeep);
IAnyResource personToDelete = getPersonFromIdOrThrowException(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE, thePersonIdToDelete.getValue());
IAnyResource personToKeep = getPersonFromIdOrThrowException(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP, thePersonIdToKeep.getValue());
validateMergeResources(personToDelete, personToKeep);
validateMergeParameters(theFromPersonId, theToPersonId);
IAnyResource fromPerson = getLatestPersonFromIdOrThrowException(ProviderConstants.EMPI_MERGE_PERSONS_FROM_PERSON_ID, theFromPersonId.getValue());
IAnyResource toPerson = getLatestPersonFromIdOrThrowException(ProviderConstants.EMPI_MERGE_PERSONS_TO_PERSON_ID, theToPersonId.getValue());
validateMergeResources(fromPerson, toPerson);
validateSameVersion(fromPerson, theFromPersonId);
validateSameVersion(toPerson, theToPersonId);
return (Person) myPersonMergerSvc.mergePersons(personToDelete, personToKeep, createEmpiContext(theRequestDetails));
return (Person) myPersonMergerSvc.mergePersons(fromPerson, toPerson, createEmpiContext(theRequestDetails));
}
@Operation(name = ProviderConstants.EMPI_UPDATE_LINK, type = Person.class)
@ -106,8 +108,10 @@ public class EmpiProviderDstu3 extends BaseEmpiProvider {
validateUpdateLinkParameters(thePersonId, theTargetId, theMatchResult);
EmpiMatchResultEnum matchResult = extractMatchResultOrNull(theMatchResult);
IAnyResource person = getPersonFromIdOrThrowException(ProviderConstants.EMPI_UPDATE_LINK_PERSON_ID, thePersonId.getValue());
IAnyResource target = getTargetFromIdOrThrowException(ProviderConstants.EMPI_UPDATE_LINK_TARGET_ID, theTargetId.getValue());
IAnyResource person = getLatestPersonFromIdOrThrowException(ProviderConstants.EMPI_UPDATE_LINK_PERSON_ID, thePersonId.getValue());
IAnyResource target = getLatestTargetFromIdOrThrowException(ProviderConstants.EMPI_UPDATE_LINK_TARGET_ID, theTargetId.getValue());
validateSameVersion(person, thePersonId);
validateSameVersion(target, theTargetId);
return (Person) myEmpiLinkUpdaterSvc.updateLink(person, target, matchResult, createEmpiContext(theRequestDetails));
}

View File

@ -88,15 +88,17 @@ public class EmpiProviderR4 extends BaseEmpiProvider {
}
@Operation(name = ProviderConstants.EMPI_MERGE_PERSONS, type = Person.class)
public Person mergePersons(@OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE, min = 1, max = 1) StringType thePersonIdToDelete,
@OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP, min = 1, max = 1) StringType thePersonIdToKeep,
public Person mergePersons(@OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_FROM_PERSON_ID, min = 1, max = 1) StringType theFromPersonId,
@OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_TO_PERSON_ID, min = 1, max = 1) StringType theToPersonId,
RequestDetails theRequestDetails) {
validateMergeParameters(thePersonIdToDelete, thePersonIdToKeep);
IAnyResource personToDelete = getPersonFromIdOrThrowException(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE, thePersonIdToDelete.getValue());
IAnyResource personToKeep = getPersonFromIdOrThrowException(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP, thePersonIdToKeep.getValue());
validateMergeResources(personToDelete, personToKeep);
validateMergeParameters(theFromPersonId, theToPersonId);
IAnyResource fromPerson = getLatestPersonFromIdOrThrowException(ProviderConstants.EMPI_MERGE_PERSONS_FROM_PERSON_ID, theFromPersonId.getValue());
IAnyResource toPerson = getLatestPersonFromIdOrThrowException(ProviderConstants.EMPI_MERGE_PERSONS_TO_PERSON_ID, theToPersonId.getValue());
validateMergeResources(fromPerson, toPerson);
validateSameVersion(fromPerson, theFromPersonId);
validateSameVersion(toPerson, theToPersonId);
return (Person) myPersonMergerSvc.mergePersons(personToDelete, personToKeep, createEmpiContext(theRequestDetails));
return (Person) myPersonMergerSvc.mergePersons(fromPerson, toPerson, createEmpiContext(theRequestDetails));
}
@Operation(name = ProviderConstants.EMPI_UPDATE_LINK, type = Person.class)
@ -107,8 +109,10 @@ public class EmpiProviderR4 extends BaseEmpiProvider {
validateUpdateLinkParameters(thePersonId, theTargetId, theMatchResult);
EmpiMatchResultEnum matchResult = extractMatchResultOrNull(theMatchResult);
IAnyResource person = getPersonFromIdOrThrowException(ProviderConstants.EMPI_UPDATE_LINK_PERSON_ID, thePersonId.getValue());
IAnyResource target = getTargetFromIdOrThrowException(ProviderConstants.EMPI_UPDATE_LINK_TARGET_ID, theTargetId.getValue());
IAnyResource person = getLatestPersonFromIdOrThrowException(ProviderConstants.EMPI_UPDATE_LINK_PERSON_ID, thePersonId.getValue());
IAnyResource target = getLatestTargetFromIdOrThrowException(ProviderConstants.EMPI_UPDATE_LINK_TARGET_ID, theTargetId.getValue());
validateSameVersion(person, thePersonId);
validateSameVersion(target, theTargetId);
return (Person) myEmpiLinkUpdaterSvc.updateLink(person, target, matchResult, createEmpiContext(theRequestDetails));
}

View File

@ -57,4 +57,10 @@ public final class EmpiUtil {
public static boolean isEmpiManaged(IBaseResource theBaseResource) {
return theBaseResource.getMeta().getTag(EmpiConstants.SYSTEM_EMPI_MANAGED, EmpiConstants.CODE_HAPI_EMPI_MANAGED) != null;
}
public static boolean isEmpiManagedPerson(FhirContext theFhirContext, IBaseResource theResource) {
String resourceType = theFhirContext.getResourceType(theResource);
return "Person".equals(resourceType) && isEmpiManaged(theResource);
}
}

View File

@ -222,16 +222,18 @@ public class PersonHelper {
switch (myFhirContext.getVersion().getVersion()) {
case R4:
Person personR4 = new Person();
personR4.setActive(true);
eidsToApply.forEach(eid -> personR4.addIdentifier(eid.toR4()));
personR4.getMeta().addTag((Coding) buildEmpiManagedTag());
copyEmpiTargetDataIntoPerson(theSourceResource, personR4, true);
return personR4;
case DSTU3:
org.hl7.fhir.dstu3.model.Person personDSTU3 = new org.hl7.fhir.dstu3.model.Person();
eidsToApply.forEach(eid -> personDSTU3.addIdentifier(eid.toDSTU3()));
personDSTU3.getMeta().addTag((org.hl7.fhir.dstu3.model.Coding) buildEmpiManagedTag());
copyEmpiTargetDataIntoPerson(theSourceResource, personDSTU3, true);
return personDSTU3;
org.hl7.fhir.dstu3.model.Person personDstu3 = new org.hl7.fhir.dstu3.model.Person();
personDstu3.setActive(true);
eidsToApply.forEach(eid -> personDstu3.addIdentifier(eid.toDSTU3()));
personDstu3.getMeta().addTag((org.hl7.fhir.dstu3.model.Coding) buildEmpiManagedTag());
copyEmpiTargetDataIntoPerson(theSourceResource, personDstu3, true);
return personDstu3;
default:
throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion());
}
@ -466,22 +468,22 @@ public class PersonHelper {
}
}
public void mergePersonFields(IBaseResource thePersonToDelete, IBaseResource thePersonToKeep) {
public void mergePersonFields(IBaseResource theFromPerson, IBaseResource theToPerson) {
switch (myFhirContext.getVersion().getVersion()) {
case R4:
mergeR4PersonFields(thePersonToDelete, thePersonToKeep);
mergeR4PersonFields(theFromPerson, theToPerson);
break;
case DSTU3:
mergeDstu3PersonFields(thePersonToDelete, thePersonToKeep);
mergeDstu3PersonFields(theFromPerson, theToPerson);
break;
default:
throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion());
}
}
private void mergeR4PersonFields(IBaseResource thePersonToDelete, IBaseResource thePersonToKeep) {
Person fromPerson = (Person) thePersonToDelete;
Person toPerson = (Person) thePersonToKeep;
private void mergeR4PersonFields(IBaseResource theFromPerson, IBaseResource theToPerson) {
Person fromPerson = (Person) theFromPerson;
Person toPerson = (Person) theToPerson;
mergeElementList(fromPerson, toPerson, HumanName.class, Person::getName, HumanName::equalsDeep);
mergeElementList(fromPerson, toPerson, Identifier.class, Person::getIdentifier, Identifier::equalsDeep);
@ -511,9 +513,9 @@ public class PersonHelper {
toList.addAll(itemsToAdd);
}
private void mergeDstu3PersonFields(IBaseResource thePersonToDelete, IBaseResource thePersonToKeep) {
org.hl7.fhir.dstu3.model.Person fromPerson = (org.hl7.fhir.dstu3.model.Person) thePersonToDelete;
org.hl7.fhir.dstu3.model.Person toPerson = (org.hl7.fhir.dstu3.model.Person) thePersonToKeep;
private void mergeDstu3PersonFields(IBaseResource theFromPerson, IBaseResource theToPerson) {
org.hl7.fhir.dstu3.model.Person fromPerson = (org.hl7.fhir.dstu3.model.Person) theFromPerson;
org.hl7.fhir.dstu3.model.Person toPerson = (org.hl7.fhir.dstu3.model.Person) theToPerson;
mergeElementList(fromPerson, toPerson, org.hl7.fhir.dstu3.model.HumanName.class, org.hl7.fhir.dstu3.model.Person::getName, org.hl7.fhir.dstu3.model.HumanName::equalsDeep);
mergeElementList(fromPerson, toPerson, org.hl7.fhir.dstu3.model.Identifier.class, org.hl7.fhir.dstu3.model.Person::getIdentifier, org.hl7.fhir.dstu3.model.Identifier::equalsDeep);
@ -627,4 +629,32 @@ public class PersonHelper {
updatePersonExternalEidFromEmpiTarget(thePerson, theResource, theEmpiTransactionContext);
}
}
public void deactivatePerson(IAnyResource thePerson) {
switch (myFhirContext.getVersion().getVersion()) {
case R4:
Person personR4 = (Person) thePerson;
personR4.setActive(false);
break;
case DSTU3:
org.hl7.fhir.dstu3.model.Person personStu3 = (org.hl7.fhir.dstu3.model.Person) thePerson;
personStu3.setActive(false);
break;
default:
throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion());
}
}
public boolean isDeactivated(IBaseResource thePerson) {
switch (myFhirContext.getVersion().getVersion()) {
case R4:
Person personR4 = (Person) thePerson;
return !personR4.getActive();
case DSTU3:
org.hl7.fhir.dstu3.model.Person personStu3 = (org.hl7.fhir.dstu3.model.Person) thePerson;
return !personStu3.getActive();
default:
throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion());
}
}
}

View File

@ -556,6 +556,10 @@ public abstract class RequestDetails {
@Override
public Object callHooksAndReturnObject(Pointcut thePointcut, HookParams theParams) {
if (!thePointcut.getReturnType().equals(void.class)) {
return myWrap.callHooksAndReturnObject(thePointcut, theParams);
}
myDeferredTasks.add(() -> myWrap.callHooksAndReturnObject(thePointcut, theParams));
return null;
}

View File

@ -65,8 +65,8 @@ public class ProviderConstants {
public static final String EMPI_MATCH_RESOURCE = "resource";
public static final String EMPI_MERGE_PERSONS = "$empi-merge-persons";
public static final String EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE = "personIdToDelete";
public static final String EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP = "personIdToKeep";
public static final String EMPI_MERGE_PERSONS_FROM_PERSON_ID = "fromPersonId";
public static final String EMPI_MERGE_PERSONS_TO_PERSON_ID = "toPersonId";
public static final String EMPI_UPDATE_LINK = "$empi-update-link";
public static final String EMPI_UPDATE_LINK_PERSON_ID = "personId";

View File

@ -28,6 +28,12 @@ public class CommonCodeSystemsTerminologyServiceTest {
assertEquals(true, outcome.isFound());
}
@Test
public void testUcum_LookupCode_Good2() {
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(new ValidationSupportContext(myCtx.getValidationSupport()), "http://unitsofmeasure.org", "kg/m2");
assertEquals(true, outcome.isFound());
}
@Test
public void testUcum_LookupCode_Bad() {
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(new ValidationSupportContext(myCtx.getValidationSupport()), "http://unitsofmeasure.org", "AAAAA");