Resolve "1366 expand operation needs to be optimized for large valuesets 3" (#1403)

* Added TRM_VALUESET.EXPANSION_STATUS column, index, fields to entity, and migration tasks.

* Incremental work on large ValueSet expansion support; still need to fix asynchronous tests and actually use the terminology tables when expanding.

* Incremental work on large ValueSet expansion support; still need to actually use the terminology tables when expanding.
This commit is contained in:
Diederik Muylwyk 2019-08-01 13:57:25 -04:00 committed by GitHub
commit 33b4f7537f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 581 additions and 154 deletions

View File

@ -26,10 +26,15 @@ import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
public interface ITermValueSetConceptDao extends JpaRepository<TermValueSetConcept, Long> {
@Query("DELETE FROM TermValueSetConcept vsc WHERE vsc.myValueSet.myId = :pid")
@Modifying
void deleteTermValueSetConceptsByValueSetId(@Param("pid") Long theValueSetId);
void deleteByTermValueSetId(@Param("pid") Long theValueSetId);
@Query("SELECT vsc FROM TermValueSetConcept vsc WHERE vsc.myValueSet.myId = :pid AND vsc.mySystem = :system_url AND vsc.myCode = :codeval")
Optional<TermValueSetConcept> findByValueSetIdSystemAndCode(@Param("pid") Long theValueSetId, @Param("system_url") String theSystem, @Param("codeval") String theCode);
}

View File

@ -30,6 +30,6 @@ public interface ITermValueSetConceptDesignationDao extends JpaRepository<TermVa
@Query("DELETE FROM TermValueSetConceptDesignation vscd WHERE vscd.myConcept.myValueSet.myId = :pid")
@Modifying
void deleteTermValueSetConceptDesignationsByValueSetId(@Param("pid") Long theValueSetId);
void deleteByTermValueSetId(@Param("pid") Long theValueSetId);
}

View File

@ -21,6 +21,9 @@ package ca.uhn.fhir.jpa.dao.data;
*/
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetExpansionStatusEnum;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
@ -32,7 +35,7 @@ public interface ITermValueSetDao extends JpaRepository<TermValueSet, Long> {
@Query("DELETE FROM TermValueSet vs WHERE vs.myId = :pid")
@Modifying
void deleteTermValueSetById(@Param("pid") Long theId);
void deleteByTermValueSetId(@Param("pid") Long theId);
@Query("SELECT vs FROM TermValueSet vs WHERE vs.myResourcePid = :resource_pid")
Optional<TermValueSet> findByResourcePid(@Param("resource_pid") Long theResourcePid);
@ -40,4 +43,7 @@ public interface ITermValueSetDao extends JpaRepository<TermValueSet, Long> {
@Query("SELECT vs FROM TermValueSet vs WHERE vs.myUrl = :url")
Optional<TermValueSet> findByUrl(@Param("url") String theUrl);
@Query("SELECT vs FROM TermValueSet vs WHERE vs.myExpansionStatus = :expansion_status")
Page<TermValueSet> findByExpansionStatus(Pageable pageable, @Param("expansion_status") TermValueSetExpansionStatusEnum theExpansionStatus);
}

View File

@ -315,7 +315,7 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3<ValueSet>
try {
ValueSet valueSet = (ValueSet) theResource;
org.hl7.fhir.r4.model.ValueSet converted = VersionConvertor_30_40.convertValueSet(valueSet);
myHapiTerminologySvc.storeTermValueSetAndChildren(retVal, converted);
myHapiTerminologySvc.storeTermValueSet(retVal, converted);
} catch (FHIRException fe) {
throw new InternalErrorException(fe);
}

View File

@ -313,7 +313,7 @@ public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4<ValueSet> imple
if (myDaoConfig.isPreExpandValueSetsExperimental()) {
if (retVal.getDeleted() == null) {
ValueSet valueSet = (ValueSet) theResource;
myHapiTerminologySvc.storeTermValueSetAndChildren(retVal, valueSet);
myHapiTerminologySvc.storeTermValueSet(retVal, valueSet);
} else {
myHapiTerminologySvc.deleteValueSetAndChildren(retVal);
}

View File

@ -43,6 +43,7 @@ import static org.apache.commons.lang3.StringUtils.length;
public class TermValueSet implements Serializable {
private static final long serialVersionUID = 1L;
public static final int MAX_EXPANSION_STATUS_LENGTH = 50;
public static final int MAX_NAME_LENGTH = 200;
public static final int MAX_URL_LENGTH = 200;
@ -68,6 +69,15 @@ public class TermValueSet implements Serializable {
@OneToMany(mappedBy = "myValueSet")
private List<TermValueSetConcept> myConcepts;
@Enumerated(EnumType.STRING)
@Column(name = "EXPANSION_STATUS", nullable = false, length = MAX_EXPANSION_STATUS_LENGTH)
private TermValueSetExpansionStatusEnum myExpansionStatus;
public TermValueSet() {
super();
myExpansionStatus = TermValueSetExpansionStatusEnum.NOT_EXPANDED;
}
public Long getId() {
return myId;
}
@ -110,6 +120,14 @@ public class TermValueSet implements Serializable {
return myConcepts;
}
public TermValueSetExpansionStatusEnum getExpansionStatus() {
return myExpansionStatus;
}
public void setExpansionStatus(TermValueSetExpansionStatusEnum theExpansionStatus) {
myExpansionStatus = theExpansionStatus;
}
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
@ -139,6 +157,7 @@ public class TermValueSet implements Serializable {
.append("myResourcePid", myResourcePid)
.append("myName", myName)
.append(myConcepts != null ? ("myConcepts - size=" + myConcepts.size()) : ("myConcepts=(null)"))
.append("myExpansionStatus", myExpansionStatus)
.toString();
}
}

View File

@ -35,8 +35,12 @@ import java.util.List;
import static org.apache.commons.lang3.StringUtils.left;
import static org.apache.commons.lang3.StringUtils.length;
@Table(name = "TRM_VALUESET_CONCEPT", indexes = {
@Index(name = "IDX_VALUESET_CONCEPT_CS_CD", columnList = "SYSTEM_URL, CODEVAL")
/*
* DM 2019-08-01 - Do not use IDX_VALUESET_CONCEPT_CS_CD; this was previously used as an index so reusing the name will
* bork up migration tasks.
*/
@Table(name = "TRM_VALUESET_CONCEPT", uniqueConstraints = {
@UniqueConstraint(name = "IDX_VS_CONCEPT_CS_CD", columnNames = {"VALUESET_PID", "SYSTEM_URL", "CODEVAL"})
})
@Entity()
public class TermValueSetConcept implements Serializable {

View File

@ -0,0 +1,42 @@
package ca.uhn.fhir.jpa.entity;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 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%
*/
/**
* This enum is used to indicate the expansion status of a given ValueSet in the terminology tables. In this context,
* an expanded ValueSet has its included concepts stored in the terminology tables as well.
*/
public enum TermValueSetExpansionStatusEnum {
/**
* This status indicates the ValueSet is waiting to be picked up and expanded by a scheduled task.
*/
NOT_EXPANDED,
/**
* This status indicates the ValueSet has been picked up by a scheduled task and is mid-expansion.
*/
EXPANSION_IN_PROGRESS,
/**
* This status indicates the ValueSet has been picked up by a scheduled task and expansion is complete.
*/
EXPANDED
}

View File

@ -101,6 +101,8 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
private static boolean ourLastResultsFromTranslationCache; // For testing.
private static boolean ourLastResultsFromTranslationWithReverseCache; // For testing.
@Autowired
protected DaoRegistry myDaoRegistry;
@Autowired
protected ITermCodeSystemDao myCodeSystemDao;
@Autowired
protected ITermConceptDao myConceptDao;
@ -151,8 +153,10 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
private PlatformTransactionManager myTransactionManager;
@Autowired(required = false)
private IFulltextSearchSvc myFulltextSearchSvc;
@Autowired
private PlatformTransactionManager myTxManager;
private void addCodeIfNotAlreadyAdded(IValueSetCodeAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, TermConcept theConcept, boolean theAdd, AtomicInteger theCodeCounter) {
private void addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, TermConcept theConcept, boolean theAdd, AtomicInteger theCodeCounter) {
String codeSystem = theConcept.getCodeSystemVersion().getCodeSystem().getCodeSystemUri();
String code = theConcept.getCode();
String display = theConcept.getDisplay();
@ -160,28 +164,28 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, designations, theAdd, theCodeCounter, codeSystem, code, display);
}
private void addCodeIfNotAlreadyAdded(IValueSetCodeAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, Collection<TermConceptDesignation> theDesignations, boolean theAdd, AtomicInteger theCodeCounter, String theCodeSystem, String theCode, String theDisplay) {
private void addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, Collection<TermConceptDesignation> theDesignations, boolean theAdd, AtomicInteger theCodeCounter, String theCodeSystem, String theCode, String theDisplay) {
if (isNoneBlank(theCodeSystem, theCode)) {
if (theAdd && theAddedCodes.add(theCodeSystem + "|" + theCode)) {
theValueSetCodeAccumulator.includeCodeWithDesignations(theCodeSystem, theCode, theDisplay, theDesignations);
theValueSetCodeAccumulator.includeConceptWithDesignations(theCodeSystem, theCode, theDisplay, theDesignations);
theCodeCounter.incrementAndGet();
}
if (!theAdd && theAddedCodes.remove(theCodeSystem + "|" + theCode)) {
theValueSetCodeAccumulator.excludeCode(theCodeSystem, theCode);
theValueSetCodeAccumulator.excludeConcept(theCodeSystem, theCode);
theCodeCounter.decrementAndGet();
}
}
}
private void addConceptsToList(IValueSetCodeAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, String theSystem, List<CodeSystem.ConceptDefinitionComponent> theConcept, boolean theAdd) {
private void addConceptsToList(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, String theSystem, List<CodeSystem.ConceptDefinitionComponent> theConcept, boolean theAdd) {
for (CodeSystem.ConceptDefinitionComponent next : theConcept) {
if (isNoneBlank(theSystem, next.getCode())) {
if (theAdd && theAddedCodes.add(theSystem + "|" + next.getCode())) {
theValueSetCodeAccumulator.includeCode(theSystem, next.getCode(), next.getDisplay());
theValueSetCodeAccumulator.includeConcept(theSystem, next.getCode(), next.getDisplay());
}
if (!theAdd && theAddedCodes.remove(theSystem + "|" + next.getCode())) {
theValueSetCodeAccumulator.excludeCode(theSystem, next.getCode());
theValueSetCodeAccumulator.excludeConcept(theSystem, next.getCode());
}
}
addConceptsToList(theValueSetCodeAccumulator, theAddedCodes, theSystem, next.getConcept(), theAdd);
@ -389,9 +393,9 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
TermValueSet existingTermValueSet = optionalExistingTermValueSetById.get();
ourLog.info("Deleting existing TermValueSet {} and its children...", existingTermValueSet.getId());
myValueSetConceptDesignationDao.deleteTermValueSetConceptDesignationsByValueSetId(existingTermValueSet.getId());
myValueSetConceptDao.deleteTermValueSetConceptsByValueSetId(existingTermValueSet.getId());
myValueSetDao.deleteTermValueSetById(existingTermValueSet.getId());
myValueSetConceptDesignationDao.deleteByTermValueSetId(existingTermValueSet.getId());
myValueSetConceptDao.deleteByTermValueSetId(existingTermValueSet.getId());
myValueSetDao.deleteByTermValueSetId(existingTermValueSet.getId());
ourLog.info("Done deleting existing TermValueSet {} and its children.", existingTermValueSet.getId());
ourLog.info("Flushing...");
@ -457,7 +461,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
@Transactional(propagation = Propagation.REQUIRED)
public ValueSet expandValueSet(ValueSet theValueSetToExpand) {
ValueSetExpansionComponentWithCodeAccumulator expansionComponent = new ValueSetExpansionComponentWithCodeAccumulator();
ValueSetExpansionComponentWithConceptAccumulator expansionComponent = new ValueSetExpansionComponentWithConceptAccumulator();
expansionComponent.setIdentifier(UUID.randomUUID().toString());
expansionComponent.setTimestamp(new Date());
@ -476,11 +480,11 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void expandValueSet(ValueSet theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator) {
public void expandValueSet(ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
expandValueSet(theValueSetToExpand, theValueSetCodeAccumulator, new AtomicInteger(0));
}
private void expandValueSet(ValueSet theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator, AtomicInteger theCodeCounter) {
private void expandValueSet(ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator, AtomicInteger theCodeCounter) {
Set<String> addedCodes = new HashSet<>();
// Handle includes
@ -509,7 +513,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
return retVal;
}
private void expandValueSetHandleIncludeOrExclude(IValueSetCodeAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, ValueSet.ConceptSetComponent theInclude, boolean theAdd, AtomicInteger theCodeCounter) {
private void expandValueSetHandleIncludeOrExclude(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, ValueSet.ConceptSetComponent theInclude, boolean theAdd, AtomicInteger theCodeCounter) {
String system = theInclude.getSystem();
boolean hasSystem = isNotBlank(system);
boolean hasValueSet = theInclude.getValueSet().size() > 0;
@ -676,10 +680,10 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
CodeSystem.ConceptDefinitionComponent code = findCode(codeSystemFromContext.getConcept(), nextCode);
if (code != null) {
if (theAdd && theAddedCodes.add(system + "|" + nextCode)) {
theValueSetCodeAccumulator.includeCode(system, nextCode, code.getDisplay());
theValueSetCodeAccumulator.includeConcept(system, nextCode, code.getDisplay());
}
if (!theAdd && theAddedCodes.remove(system + "|" + nextCode)) {
theValueSetCodeAccumulator.excludeCode(system, nextCode);
theValueSetCodeAccumulator.excludeConcept(system, nextCode);
}
}
}
@ -706,7 +710,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
}
if (isNoneBlank(nextConcept.getSystem(), nextConcept.getCode()) && !theAdd && theAddedCodes.remove(nextConcept.getSystem() + "|" + nextConcept.getCode())) {
theValueSetCodeAccumulator.excludeCode(nextConcept.getSystem(), nextConcept.getCode());
theValueSetCodeAccumulator.excludeConcept(nextConcept.getSystem(), nextConcept.getCode());
}
}
@ -716,10 +720,10 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
}
}
private void expandWithoutHibernateSearch(IValueSetCodeAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, ValueSet.ConceptSetComponent theInclude, String theSystem, boolean theAdd, AtomicInteger theCodeCounter) {
private void expandWithoutHibernateSearch(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, ValueSet.ConceptSetComponent theInclude, String theSystem, boolean theAdd, AtomicInteger theCodeCounter) {
ourLog.trace("Hibernate search is not enabled");
if (theValueSetCodeAccumulator instanceof ValueSetExpansionComponentWithCodeAccumulator) {
Validate.isTrue(((ValueSetExpansionComponentWithCodeAccumulator) theValueSetCodeAccumulator).getParameter().isEmpty(), "Can not expand ValueSet with parameters - Hibernate Search is not enabled on this server.");
if (theValueSetCodeAccumulator instanceof ValueSetExpansionComponentWithConceptAccumulator) {
Validate.isTrue(((ValueSetExpansionComponentWithConceptAccumulator) theValueSetCodeAccumulator).getParameter().isEmpty(), "Can not expand ValueSet with parameters - Hibernate Search is not enabled on this server.");
}
Validate.isTrue(theInclude.getFilter().isEmpty(), "Can not expand ValueSet with filters - Hibernate Search is not enabled on this server.");
Validate.isTrue(isNotBlank(theSystem), "Can not expand ValueSet without explicit system - Hibernate Search is not enabled on this server.");
@ -1491,9 +1495,45 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
ourLog.info("Done storing TermConceptMap.");
}
@Scheduled(fixedDelay = 600000) // 10 minutes.
@Override
public synchronized void preExpandValueSetToTerminologyTables() {
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
Optional<TermValueSet> optionalTermValueSet = getNextTermValueSetNotExpanded();
if (optionalTermValueSet.isPresent()) {
TermValueSet termValueSet = optionalTermValueSet.get();
termValueSet.setExpansionStatus(TermValueSetExpansionStatusEnum.EXPANSION_IN_PROGRESS);
myValueSetDao.saveAndFlush(termValueSet);
ValueSet valueSet = getValueSetFromResourceTable(termValueSet.getResource());
expandValueSet(valueSet, new ValueSetConceptAccumulator(termValueSet, myValueSetConceptDao, myValueSetConceptDesignationDao));
termValueSet.setExpansionStatus(TermValueSetExpansionStatusEnum.EXPANDED);
myValueSetDao.saveAndFlush(termValueSet);
}
}
});
}
protected abstract ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable);
private Optional<TermValueSet> getNextTermValueSetNotExpanded() {
Optional<TermValueSet> retVal = Optional.empty();
Page<TermValueSet> page = myValueSetDao.findByExpansionStatus(PageRequest.of(0, 1), TermValueSetExpansionStatusEnum.NOT_EXPANDED);
if (!page.getContent().isEmpty()) {
retVal = Optional.of(page.getContent().get(0));
}
return retVal;
}
@Override
@Transactional
public void storeTermValueSetAndChildren(ResourceTable theResourceTable, ValueSet theValueSet) {
public void storeTermValueSet(ResourceTable theResourceTable, ValueSet theValueSet) {
ourLog.info("Storing TermValueSet {}", theValueSet.getIdElement().getValue());
ValidateUtil.isTrueOrThrowInvalidRequest(theResourceTable != null, "No resource supplied");
@ -1513,58 +1553,8 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
String url = termValueSet.getUrl();
Optional<TermValueSet> optionalExistingTermValueSetByUrl = myValueSetDao.findByUrl(url);
if (!optionalExistingTermValueSetByUrl.isPresent()) {
myValueSetDao.save(termValueSet);
int conceptsSaved = 0;
int designationsSaved = 0;
// FIXME: DM 2019-07-15 - Here we should call expandValueSet(ValueSet theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator).
// FIXME: DM 2019-07-15 - We need an implementation IValueSetCodeAccumulator that saves ValueSetConcept records and their children.
ValueSet expandedValueSet = expandValueSet(theValueSet);
if (expandedValueSet.hasExpansion()) {
if (expandedValueSet.getExpansion().hasTotal() && expandedValueSet.getExpansion().getTotal() > 0) {
TermValueSetConcept concept;
for (ValueSet.ValueSetExpansionContainsComponent contains : expandedValueSet.getExpansion().getContains()) {
ValidateUtil.isNotBlankOrThrowInvalidRequest(contains.getSystem(), "ValueSet contains a concept with no system value");
ValidateUtil.isNotBlankOrThrowInvalidRequest(contains.getCode(), "ValueSet contains a concept with no code value");
concept = new TermValueSetConcept();
concept.setValueSet(termValueSet);
concept.setSystem(contains.getSystem());
concept.setCode(contains.getCode());
concept.setDisplay(contains.hasDisplay() ? contains.getDisplay() : null);
myValueSetConceptDao.save(concept);
TermValueSetConceptDesignation designation;
for (ValueSet.ConceptReferenceDesignationComponent containedDesignation : contains.getDesignation()) {
ValidateUtil.isNotBlankOrThrowInvalidRequest(containedDesignation.getValue(), "ValueSet contains a concept designation with no value");
designation = new TermValueSetConceptDesignation();
designation.setConcept(concept);
designation.setLanguage(containedDesignation.hasLanguage() ? containedDesignation.getLanguage() : null);
if (containedDesignation.hasUse()) {
designation.setUseSystem(containedDesignation.getUse().hasSystem() ? containedDesignation.getUse().getSystem() : null);
designation.setUseCode(containedDesignation.getUse().hasCode() ? containedDesignation.getUse().getCode() : null);
designation.setUseDisplay(containedDesignation.getUse().hasDisplay() ? containedDesignation.getUse().getDisplay() : null);
}
designation.setValue(containedDesignation.getValue());
myValueSetConceptDesignationDao.save(designation);
if (designationsSaved++ % 250 == 0) {
ourLog.info("Have pre-expanded {} designations in ValueSet", designationsSaved);
myValueSetConceptDesignationDao.flush();
}
}
// TODO: DM 2019-07-16 - We need TermValueSetConceptProperty, similar to TermConceptProperty.
// TODO: DM 2019-07-16 - We should also populate TermValueSetConceptProperty entities here.
if (conceptsSaved++ % 250 == 0) {
ourLog.info("Have pre-expanded {} concepts in ValueSet", conceptsSaved);
myValueSetConceptDao.flush();
}
}
}
}
} else {
TermValueSet existingTermValueSet = optionalExistingTermValueSetByUrl.get();

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.term;
* #L%
*/
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.hapi.validation.IValidationSupport;
@ -81,13 +82,18 @@ public class HapiTerminologySvcDstu2 extends BaseHapiTerminologySvcImpl {
return null;
}
@Override
protected ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable) {
throw new UnsupportedOperationException();
}
@Override
public IBaseResource expandValueSet(IBaseResource theValueSetToExpand) {
throw new UnsupportedOperationException();
}
@Override
public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator) {
public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
throw new UnsupportedOperationException();
}

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.UrlUtil;
@ -177,7 +178,7 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen
}
@Override
public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator) {
public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
ValueSet valueSetToExpand = (ValueSet) theValueSetToExpand;
try {
@ -292,6 +293,20 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen
}
}
@Override
protected org.hl7.fhir.r4.model.ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable) {
ValueSet valueSet = myValueSetResourceDao.toResource(ValueSet.class, theResourceTable, null, false);
org.hl7.fhir.r4.model.ValueSet valueSetR4;
try {
valueSetR4 = VersionConvertor_30_40.convertValueSet(valueSet);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
return valueSetR4;
}
@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
return myTerminologySvc.supportsSystem(theSystem);

View File

@ -3,9 +3,9 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.ValidateUtil;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
@ -138,7 +138,7 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements
}
@Override
public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator) {
public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
ValueSet valueSetToExpand = (ValueSet) theValueSetToExpand;
super.expandValueSet(valueSetToExpand, theValueSetCodeAccumulator);
}
@ -231,6 +231,11 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements
return myValidationSupport.fetchCodeSystem(myContext, theSystem);
}
@Override
protected ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable) {
return myValueSetResourceDao.toResource(ValueSet.class, theResourceTable, null, false);
}
@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
return supportsSystem(theSystem);

View File

@ -44,14 +44,14 @@ public interface IHapiTerminologySvc {
ValueSet expandValueSet(ValueSet theValueSetToExpand);
void expandValueSet(ValueSet theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator);
void expandValueSet(ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator);
/**
* Version independent
*/
IBaseResource expandValueSet(IBaseResource theValueSetToExpand);
void expandValueSet(IBaseResource theValueSetToExpand, IValueSetCodeAccumulator theValueSetCodeAccumulator);
void expandValueSet(IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator);
List<VersionIndependentConcept> expandValueSet(String theValueSet);
@ -94,7 +94,7 @@ public interface IHapiTerminologySvc {
void storeTermConceptMapAndChildren(ResourceTable theResourceTable, ConceptMap theConceptMap);
void storeTermValueSetAndChildren(ResourceTable theResourceTable, ValueSet theValueSet);
void storeTermValueSet(ResourceTable theResourceTable, ValueSet theValueSet);
boolean supportsSystem(String theCodeSystem);
@ -107,4 +107,6 @@ public interface IHapiTerminologySvc {
AtomicInteger applyDeltaCodesystemsAdd(String theSystem, @Nullable String theParent, CodeSystem theValue);
AtomicInteger applyDeltaCodesystemsRemove(String theSystem, CodeSystem theDelta);
void preExpandValueSetToTerminologyTables();
}

View File

@ -24,12 +24,12 @@ import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
import java.util.Collection;
public interface IValueSetCodeAccumulator {
public interface IValueSetConceptAccumulator {
void includeCode(String theSystem, String theCode, String theDisplay);
void includeConcept(String theSystem, String theCode, String theDisplay);
void includeCodeWithDesignations(String theSystem, String theCode, String theDisplay, Collection<TermConceptDesignation> theDesignations);
void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, Collection<TermConceptDesignation> theDesignations);
void excludeCode(String theSystem, String theCode);
void excludeConcept(String theSystem, String theCode);
}

View File

@ -0,0 +1,141 @@
package ca.uhn.fhir.jpa.term;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 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.dao.data.ITermValueSetConceptDao;
import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDesignationDao;
import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
import ca.uhn.fhir.util.ValidateUtil;
import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.*;
public class ValueSetConceptAccumulator implements IValueSetConceptAccumulator {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValueSetConceptAccumulator.class);
private TermValueSet myTermValueSet;
private ITermValueSetConceptDao myValueSetConceptDao;
private ITermValueSetConceptDesignationDao myValueSetConceptDesignationDao;
private int myConceptsSaved;
private int myDesignationsSaved;
public ValueSetConceptAccumulator(@Nonnull TermValueSet theTermValueSet, @Nonnull ITermValueSetConceptDao theValueSetConceptDao, @Nonnull ITermValueSetConceptDesignationDao theValueSetConceptDesignationDao) {
myTermValueSet = theTermValueSet;
myValueSetConceptDao = theValueSetConceptDao;
myValueSetConceptDesignationDao = theValueSetConceptDesignationDao;
myConceptsSaved = 0;
myDesignationsSaved = 0;
}
@Override
public void includeConcept(String theSystem, String theCode, String theDisplay) {
saveConcept(theSystem, theCode, theDisplay);
}
@Override
public void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, Collection<TermConceptDesignation> theDesignations) {
TermValueSetConcept concept = saveConcept(theSystem, theCode, theDisplay);
for (TermConceptDesignation designation : theDesignations) {
saveConceptDesignation(concept, designation);
}
}
@Override
public void excludeConcept(String theSystem, String theCode) {
if (isAnyBlank(theSystem, theCode)) {
return;
}
// Get existing entity so it can be deleted.
Optional<TermValueSetConcept> optionalConcept = myValueSetConceptDao.findByValueSetIdSystemAndCode(myTermValueSet.getId(), theSystem, theCode);
if (optionalConcept.isPresent()) {
TermValueSetConcept concept = optionalConcept.get();
ourLog.info("Excluding [{}|{}] from ValueSet[{}]", concept.getSystem(), concept.getCode(), myTermValueSet.getUrl());
for (TermValueSetConceptDesignation designation : concept.getDesignations()) {
myValueSetConceptDesignationDao.deleteById(designation.getId());
}
myValueSetConceptDao.deleteById(concept.getId());
ourLog.info("Done excluding [{}|{}] from ValueSet[{}]", concept.getSystem(), concept.getCode(), myTermValueSet.getUrl());
ourLog.info("Flushing...");
myValueSetConceptDesignationDao.flush();
myValueSetConceptDao.flush();
ourLog.info("Done flushing.");
}
}
private TermValueSetConcept saveConcept(String theSystem, String theCode, String theDisplay) {
ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystem, "ValueSet contains a concept with no system value");
ValidateUtil.isNotBlankOrThrowInvalidRequest(theCode, "ValueSet contains a concept with no code value");
TermValueSetConcept concept = new TermValueSetConcept();
concept.setValueSet(myTermValueSet);
concept.setSystem(theSystem);
concept.setCode(theCode);
if (isNotBlank(theDisplay)) {
concept.setDisplay(theDisplay);
}
myValueSetConceptDao.save(concept);
if (myConceptsSaved++ % 250 == 0) {
ourLog.info("Have pre-expanded {} concepts in ValueSet[{}]", myConceptsSaved, myTermValueSet.getUrl());
myValueSetConceptDao.flush();
}
return concept;
}
private TermValueSetConceptDesignation saveConceptDesignation(TermValueSetConcept theConcept, TermConceptDesignation theDesignation) {
ValidateUtil.isNotBlankOrThrowInvalidRequest(theDesignation.getValue(), "ValueSet contains a concept designation with no value");
TermValueSetConceptDesignation designation = new TermValueSetConceptDesignation();
designation.setConcept(theConcept);
designation.setLanguage(theDesignation.getLanguage());
if (isNoneBlank(theDesignation.getUseSystem(), theDesignation.getUseCode())) {
designation.setUseSystem(theDesignation.getUseSystem());
designation.setUseCode(theDesignation.getUseCode());
if (isNotBlank(theDesignation.getUseDisplay())) {
designation.setUseDisplay(theDesignation.getUseDisplay());
}
}
designation.setValue(theDesignation.getValue());
myValueSetConceptDesignationDao.save(designation);
if (myDesignationsSaved++ % 250 == 0) {
ourLog.info("Have pre-expanded {} designations in ValueSet[{}]", myDesignationsSaved, myTermValueSet.getUrl());
myValueSetConceptDesignationDao.flush();
}
return designation;
}
// TODO: DM 2019-07-16 - We need TermValueSetConceptProperty, similar to TermConceptProperty.
// TODO: DM 2019-07-16 - We should also populate TermValueSetConceptProperty entities here.
// TODO: DM 2019-07-30 - Expansions don't include the properties themselves; they are needed to facilitate filters and parameterized expansions.
}

View File

@ -27,10 +27,10 @@ import org.hl7.fhir.r4.model.ValueSet;
import java.util.Collection;
@Block()
public class ValueSetExpansionComponentWithCodeAccumulator extends ValueSet.ValueSetExpansionComponent implements IValueSetCodeAccumulator {
public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.ValueSetExpansionComponent implements IValueSetConceptAccumulator {
@Override
public void includeCode(String theSystem, String theCode, String theDisplay) {
public void includeConcept(String theSystem, String theCode, String theDisplay) {
ValueSet.ValueSetExpansionContainsComponent contains = this.addContains();
contains.setSystem(theSystem);
contains.setCode(theCode);
@ -38,7 +38,7 @@ public class ValueSetExpansionComponentWithCodeAccumulator extends ValueSet.Valu
}
@Override
public void includeCodeWithDesignations(String theSystem, String theCode, String theDisplay, Collection<TermConceptDesignation> theDesignations) {
public void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, Collection<TermConceptDesignation> theDesignations) {
ValueSet.ValueSetExpansionContainsComponent contains = this.addContains();
contains.setSystem(theSystem);
contains.setCode(theCode);
@ -58,7 +58,7 @@ public class ValueSetExpansionComponentWithCodeAccumulator extends ValueSet.Valu
}
@Override
public void excludeCode(String theSystem, String theCode) {
public void excludeConcept(String theSystem, String theCode) {
this
.getContains()
.removeIf(t ->

View File

@ -7,11 +7,11 @@ import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.model.*;
@ -22,6 +22,7 @@ import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
@ -38,12 +39,15 @@ import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@TestPropertySource(properties = {
"scheduling_disabled=true"
})
public class TerminologySvcImplR4Test extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcImplR4Test.class);
@Rule
public final ExpectedException expectedException = ExpectedException.none();
@Mock
IValueSetCodeAccumulator myValueSetCodeAccumulator;
IValueSetConceptAccumulator myValueSetCodeAccumulator;
private IIdType myConceptMapId;
private IIdType myExtensionalCsId;
private IIdType myExtensionalVsId;
@ -136,6 +140,11 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
loadAndPersistValueSet();
}
private void loadAndPersistCodeSystemAndValueSetWithDesignationsAndExclude() throws IOException {
loadAndPersistCodeSystemWithDesignations();
loadAndPersistValueSetWithExclude();
}
private void loadAndPersistCodeSystem() throws IOException {
CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml");
persistCodeSystem(codeSystem);
@ -160,6 +169,11 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
persistValueSet(valueSet);
}
private void loadAndPersistValueSetWithExclude() throws IOException {
ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs-with-exclude.xml");
persistValueSet(valueSet);
}
private void persistValueSet(ValueSet theValueSet) {
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
@ -590,7 +604,7 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
include.setSystem(CS_URL);
myTermSvc.expandValueSet(vs, myValueSetCodeAccumulator);
verify(myValueSetCodeAccumulator, times(9)).includeCodeWithDesignations(anyString(), anyString(), nullable(String.class), anyCollection());
verify(myValueSetCodeAccumulator, times(9)).includeConceptWithDesignations(anyString(), anyString(), nullable(String.class), anyCollection());
}
@Test
@ -626,15 +640,24 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
TermConcept concept = concepts.get(0);
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
assertEquals(2, concept.getDesignations().size());
TermConceptDesignation designation = concept.getDesignations().iterator().next();
List<TermConceptDesignation> designations = Lists.newArrayList(concept.getDesignations().iterator());
TermConceptDesignation designation = designations.get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
designation = designations.get(1);
assertEquals("sv", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systoliskt blodtryck - utgång", designation.getValue());
concept = concepts.get(1);
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter", concept.getDisplay());
@ -949,66 +972,187 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test {
ValueSet valueSet = myValueSetDao.read(myExtensionalVsId);
ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet));
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus theStatus) {
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsId.getIdPartAsLong());
assertTrue(optionalValueSetByResourcePid.isPresent());
runInTransaction(()->{
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsId.getIdPartAsLong());
assertTrue(optionalValueSetByResourcePid.isPresent());
Optional<TermValueSet> optionalValueSetByUrl = myTermValueSetDao.findByUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
assertTrue(optionalValueSetByUrl.isPresent());
Optional<TermValueSet> optionalValueSetByUrl = myTermValueSetDao.findByUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
assertTrue(optionalValueSetByUrl.isPresent());
TermValueSet valueSet = optionalValueSetByUrl.get();
assertSame(optionalValueSetByResourcePid.get(), valueSet);
ourLog.info("ValueSet:\n" + valueSet.toString());
assertEquals("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", valueSet.getUrl());
assertEquals("Terminology Services Connectation #1 Extensional case #2", valueSet.getName());
assertEquals(codeSystem.getConcept().size(), valueSet.getConcepts().size());
TermValueSet termValueSet = optionalValueSetByUrl.get();
assertSame(optionalValueSetByResourcePid.get(), termValueSet);
ourLog.info("ValueSet:\n" + termValueSet.toString());
assertEquals("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", termValueSet.getUrl());
assertEquals("Terminology Services Connectation #1 Extensional case #2", termValueSet.getName());
assertEquals(0, termValueSet.getConcepts().size());
assertEquals(TermValueSetExpansionStatusEnum.NOT_EXPANDED, termValueSet.getExpansionStatus());
});
TermValueSetConcept concept = valueSet.getConcepts().get(0);
ourLog.info("Code:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
myTermSvc.preExpandValueSetToTerminologyTables();
TermValueSetConceptDesignation designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
runInTransaction(()->{
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsId.getIdPartAsLong());
assertTrue(optionalValueSetByResourcePid.isPresent());
concept = valueSet.getConcepts().get(1);
ourLog.info("Code:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
Optional<TermValueSet> optionalValueSetByUrl = myTermValueSetDao.findByUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
assertTrue(optionalValueSetByUrl.isPresent());
// ...
TermValueSet termValueSet = optionalValueSetByUrl.get();
assertSame(optionalValueSetByResourcePid.get(), termValueSet);
ourLog.info("ValueSet:\n" + termValueSet.toString());
assertEquals("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", termValueSet.getUrl());
assertEquals("Terminology Services Connectation #1 Extensional case #2", termValueSet.getName());
assertEquals(codeSystem.getConcept().size(), termValueSet.getConcepts().size());
assertEquals(TermValueSetExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus());
concept = valueSet.getConcepts().get(22);
ourLog.info("Code:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8491-3", concept.getCode());
assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
TermValueSetConcept concept = termValueSet.getConcepts().get(0);
ourLog.info("Code:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration", concept.getDisplay());
assertEquals(2, concept.getDesignations().size());
designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue());
TermValueSetConceptDesignation designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
concept = valueSet.getConcepts().get(23);
ourLog.info("Code:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8492-1", concept.getCode());
assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
}
designation = concept.getDesignations().get(1);
assertEquals("sv", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systoliskt blodtryck - utgång", designation.getValue());
concept = termValueSet.getConcepts().get(1);
ourLog.info("Code:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
// ...
concept = termValueSet.getConcepts().get(22);
ourLog.info("Code:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8491-3", concept.getCode());
assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue());
concept = termValueSet.getConcepts().get(23);
ourLog.info("Code:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8492-1", concept.getCode());
assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
});
}
@Test
public void testStoreTermValueSetAndChildrenWithExclude() throws Exception {
myDaoConfig.setPreExpandValueSetsExperimental(true);
loadAndPersistCodeSystemAndValueSetWithDesignationsAndExclude();
CodeSystem codeSystem = myCodeSystemDao.read(myExtensionalCsId);
ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem));
ValueSet valueSet = myValueSetDao.read(myExtensionalVsId);
ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet));
runInTransaction(()->{
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsId.getIdPartAsLong());
assertTrue(optionalValueSetByResourcePid.isPresent());
Optional<TermValueSet> optionalValueSetByUrl = myTermValueSetDao.findByUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
assertTrue(optionalValueSetByUrl.isPresent());
TermValueSet termValueSet = optionalValueSetByUrl.get();
assertSame(optionalValueSetByResourcePid.get(), termValueSet);
ourLog.info("ValueSet:\n" + termValueSet.toString());
assertEquals("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", termValueSet.getUrl());
assertEquals("Terminology Services Connectation #1 Extensional case #2", termValueSet.getName());
assertEquals(0, termValueSet.getConcepts().size());
assertEquals(TermValueSetExpansionStatusEnum.NOT_EXPANDED, termValueSet.getExpansionStatus());
});
myTermSvc.preExpandValueSetToTerminologyTables();
runInTransaction(()->{
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsId.getIdPartAsLong());
assertTrue(optionalValueSetByResourcePid.isPresent());
Optional<TermValueSet> optionalValueSetByUrl = myTermValueSetDao.findByUrl("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
assertTrue(optionalValueSetByUrl.isPresent());
TermValueSet termValueSet = optionalValueSetByUrl.get();
assertSame(optionalValueSetByResourcePid.get(), termValueSet);
ourLog.info("ValueSet:\n" + termValueSet.toString());
assertEquals("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", termValueSet.getUrl());
assertEquals("Terminology Services Connectation #1 Extensional case #2", termValueSet.getName());
assertEquals(codeSystem.getConcept().size() - 2, termValueSet.getConcepts().size());
assertEquals(TermValueSetExpansionStatusEnum.EXPANDED, termValueSet.getExpansionStatus());
TermValueSetConcept concept = termValueSet.getConcepts().get(0);
ourLog.info("Code:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8450-9", concept.getCode());
assertEquals("Systolic blood pressure--expiration", concept.getDisplay());
assertEquals(2, concept.getDesignations().size());
TermValueSetConceptDesignation designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk - expiratie", designation.getValue());
designation = concept.getDesignations().get(1);
assertEquals("sv", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systoliskt blodtryck - utgång", designation.getValue());
concept = termValueSet.getConcepts().get(1);
ourLog.info("Code:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("11378-7", concept.getCode());
assertEquals("Systolic blood pressure at First encounter", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
// ...
concept = termValueSet.getConcepts().get(22 - 2);
ourLog.info("Code:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8491-3", concept.getCode());
assertEquals("Systolic blood pressure 1 hour minimum", concept.getDisplay());
assertEquals(1, concept.getDesignations().size());
designation = concept.getDesignations().get(0);
assertEquals("nl", designation.getLanguage());
assertEquals("http://snomed.info/sct", designation.getUseSystem());
assertEquals("900000000000013009", designation.getUseCode());
assertEquals("Synonym", designation.getUseDisplay());
assertEquals("Systolische bloeddruk minimaal 1 uur", designation.getValue());
concept = termValueSet.getConcepts().get(23 - 2);
ourLog.info("Code:\n" + concept.toString());
assertEquals("http://acme.org", concept.getSystem());
assertEquals("8492-1", concept.getCode());
assertEquals("Systolic blood pressure 8 hour minimum", concept.getDisplay());
assertEquals(0, concept.getDesignations().size());
});
}

View File

@ -12,6 +12,15 @@
</use>
<value value="Systolische bloeddruk - expiratie"/>
</designation>
<designation>
<language value="sv"/>
<use>
<system value="http://snomed.info/sct"/>
<code value="900000000000013009"/>
<display value="Synonym"/>
</use>
<value value="Systoliskt blodtryck - utgång"/>
</designation>
</concept>
<concept>
<code value="11378-7" />

View File

@ -0,0 +1,31 @@
<ValueSet xmlns="http://hl7.org/fhir">
<url value="http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2" />
<identifier>
<value value="http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2" />
</identifier>
<name value="Terminology Services Connectation #1 Extensional case #2" />
<publisher value="Grahame Grieve" />
<contact>
<telecom>
<system value="email" />
<value value="grahame@healthintersections.com.au" />
</telecom>
</contact>
<description value="an enumeration of codes defined by LOINC" />
<status value="draft" />
<experimental value="true" />
<compose>
<include>
<system value="http://acme.org"/>
</include>
<exclude>
<system value="http://acme.org"/>
<concept>
<code value="8489-7" />
</concept>
<concept>
<code value="8490-5" />
</concept>
</exclude>
</compose>
</ValueSet>

View File

@ -115,6 +115,11 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.toColumn("RES_ID")
.references("HFJ_RESOURCE", "RES_ID");
termValueSetTable.addColumn("NAME").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermValueSet.MAX_NAME_LENGTH);
termValueSetTable.addColumn("EXPANSION_STATUS").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermValueSet.MAX_EXPANSION_STATUS_LENGTH);
termValueSetTable
.addIndex("IDX_VALUESET_EXP_STATUS")
.unique(false)
.withColumns("EXPANSION_STATUS");
// TermValueSetConcept
version.startSectionWithMessage("Processing table: TRM_VALUESET_CONCEPT");
@ -128,15 +133,18 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.references("TRM_VALUESET", "PID");
termValueSetConceptTable.addColumn("SYSTEM_URL").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermCodeSystem.MAX_URL_LENGTH);
termValueSetConceptTable.addColumn("CODEVAL").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermConcept.MAX_CODE_LENGTH);
termValueSetConceptTable
.addIndex("IDX_VALUESET_CONCEPT_CS_CD")
.unique(false)
.withColumns("SYSTEM_URL", "CODEVAL");
termValueSetConceptTable.addColumn("DISPLAY").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, TermConcept.MAX_DESC_LENGTH);
version.onTable("TRM_VALUESET_CONCEPT")
.renameColumn("CODE", "CODEVAL", true, true)
.renameColumn("SYSTEM", "SYSTEM_URL", true, true);
version.startSectionWithMessage("Processing table: TRM_VALUESET_CONCEPT, swapping index for unique constraint");
termValueSetConceptTable.dropIndex("IDX_VALUESET_CONCEPT_CS_CD");
termValueSetConceptTable
.addIndex("IDX_VS_CONCEPT_CS_CD")
.unique(true)
.withColumns("VALUESET_PID", "SYSTEM_URL", "CODEVAL");
// TermValueSetConceptDesignation
version.startSectionWithMessage("Processing table: TRM_VALUESET_C_DESIGNATION");
version.addIdGenerator("SEQ_VALUESET_C_DSGNTN_PID");