Resolve memory leak (#1685)

* Work on memory leak

* Work on term delta uploading

* Resolve memoery leak in terminology service

* Fix cache key
This commit is contained in:
James Agnew 2020-01-25 17:48:26 -05:00 committed by GitHub
parent 4d523aa38c
commit ba0048aade
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 167 additions and 139 deletions

View File

@ -19,7 +19,7 @@ jobs:
steps:
- task: Cache@2
inputs:
key: 'maven | "$(Agent.OS)" | **/pom.xml'
key: 'maven | "$(Agent.OS)" | ./pom.xml'
path: $(MAVEN_CACHE_FOLDER)
- task: Bash@3
inputs:

View File

@ -82,3 +82,6 @@ Sean McIlvenna for reporting!"
title: "When using a custom structure that changes the cardinality from 0..* to 0..1, the Parser was encoding
a plain field instead of an array (as required by the FHIR specification). Thanks to
Petro Mykhailysyn for the pull request!"
- item:
type: "fix"
title: "A meomery leak was resolved in the JPA terminology service delta upload operations."

View File

@ -97,7 +97,7 @@ public class TermConcept implements Serializable {
@Column(name = "PARENT_PIDS", nullable = true)
private String myParentPids;
@OneToMany(cascade = {}, fetch = FetchType.LAZY, mappedBy = "myChild")
private Collection<TermConceptParentChildLink> myParents;
private List<TermConceptParentChildLink> myParents;
@Column(name = "CODE_SEQUENCE", nullable = true)
private Integer mySequence;
@ -269,7 +269,7 @@ public class TermConcept implements Serializable {
return myParentPids;
}
public Collection<TermConceptParentChildLink> getParents() {
public List<TermConceptParentChildLink> getParents() {
if (myParents == null) {
myParents = new ArrayList<>();
}

View File

@ -24,10 +24,21 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.dao.data.*;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptDesignationDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptParentChildLinkDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptPropertyDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.jpa.entity.TermConceptProperty;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
@ -166,10 +177,17 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
TypedQuery<TermConcept> typedQuery = myEntityManager.createQuery(query.select(root));
org.hibernate.query.Query<TermConcept> hibernateQuery = (org.hibernate.query.Query<TermConcept>) typedQuery;
ScrollableResults scrollableResults = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY);
int count = 0;
try (ScrollableResultsIterator<TermConcept> scrollableResultsIterator = new ScrollableResultsIterator<>(scrollableResults)) {
while (scrollableResultsIterator.hasNext()) {
TermConcept next = scrollableResultsIterator.next();
codeToConceptPid.put(next.getCode(), next.getId());
// We don't want to keep the loaded entities in the L1 cache because they can take up a loooot of memory
if (count % 100 == 0) {
myEntityManager.clear();
}
count++;
}
}
ourLog.info("Loaded {} concepts in {}", codeToConceptPid.size(), sw.toString());
@ -199,12 +217,20 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
String childCode = next.getChild().getCode();
parentCodeToChildCodes.put(parentCode, childCode);
childCodeToParentCodes.put(childCode, parentCode);
// We don't want to keep the loaded entities in the L1 cache because they can take up a loooot of memory
if (count % 100 == 0) {
myEntityManager.clear();
}
count++;
}
}
ourLog.info("Loaded {} parent/child relationships in {}", count, sw.toString());
}
ourLog.trace("Starting delta application");
// Account for root codes in the parent->child map
for (String nextCode : codeToConceptPid.keySet()) {
if (childCodeToParentCodes.get(nextCode).isEmpty()) {
@ -217,13 +243,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
// Add root concepts
for (TermConcept nextRootConcept : theAdditions.getRootConcepts()) {
List<String> parentCodes = Collections.emptyList();
addConcept(csv, codeToConceptPid, parentCodes, nextRootConcept, parentCodeToChildCodes, retVal, true);
}
// Add unanchored child concepts
for (TermConcept nextUnanchoredChild : theAdditions.getUnanchoredChildConceptsToParentCodes().keySet()) {
List<String> nextParentCodes = theAdditions.getUnanchoredChildConceptsToParentCodes().get(nextUnanchoredChild);
addConcept(csv, codeToConceptPid, nextParentCodes, nextUnanchoredChild, parentCodeToChildCodes, retVal, true);
addConcept(csv, codeToConceptPid, parentCodes, nextRootConcept, parentCodeToChildCodes, retVal, theAdditions.getRootConceptCodes(), true);
}
return retVal;
@ -499,10 +519,11 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
Validate.isTrue(myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3), "Terminology operations only supported in DSTU3+ mode");
}
private void addConcept(TermCodeSystemVersion theCsv, Map<String, Long> theCodeToConceptPid, Collection<String> theParentCodes, TermConcept theConceptToAdd, ListMultimap<String, String> theParentCodeToChildCodes, UploadStatistics theStatisticsTracker, boolean theForceResequence) {
TermConcept nextConceptToAdd = theConceptToAdd;
private void addConcept(TermCodeSystemVersion theCsv, Map<String, Long> theCodeToConceptPid, Collection<String> theParentCodes, TermConcept theConceptToAdd, ListMultimap<String, String> theParentCodeToChildCodes, UploadStatistics theStatisticsTracker, Set<String> theAdditionSetRootConceptCodes, boolean theRootConcept) {
TermConcept conceptToAdd = theConceptToAdd;
List<TermConceptParentChildLink> childrenToAdd = theConceptToAdd.getChildren();
String nextCodeToAdd = nextConceptToAdd.getCode();
String nextCodeToAdd = conceptToAdd.getCode();
String parentDescription = "(root concept)";
Set<TermConcept> parentConcepts = new HashSet<>();
if (!theParentCodes.isEmpty()) {
@ -522,12 +543,12 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
TermConcept existingCode = myConceptDao.getOne(theCodeToConceptPid.get(nextCodeToAdd));
existingCode.setIndexStatus(null);
existingCode.setDisplay(nextConceptToAdd.getDisplay());
nextConceptToAdd = existingCode;
existingCode.setDisplay(conceptToAdd.getDisplay());
conceptToAdd = existingCode;
}
if (theConceptToAdd.getSequence() == null || theForceResequence) {
if (conceptToAdd.getSequence() == null || !theRootConcept) {
// If this is a new code, give it a sequence number based on how many concepts the
// parent already has (or the highest number, if the code has multiple parents)
int sequence = 0;
@ -539,17 +560,15 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
theParentCodeToChildCodes.put("", nextCodeToAdd);
sequence = Math.max(sequence, theParentCodeToChildCodes.get("").size());
}
nextConceptToAdd.setSequence(sequence);
conceptToAdd.setSequence(sequence);
}
// Drop any old parent-child links if they aren't explicitly specified in the
// hierarchy being added
for (Iterator<TermConceptParentChildLink> iter = nextConceptToAdd.getParents().iterator(); iter.hasNext(); ) {
TermConceptParentChildLink nextLink = iter.next();
String parentCode = nextLink.getParent().getCode();
boolean shouldRemove = !theParentCodes.contains(parentCode);
if (shouldRemove) {
if (!theRootConcept) {
for (Iterator<TermConceptParentChildLink> iter = conceptToAdd.getParents().iterator(); iter.hasNext(); ) {
TermConceptParentChildLink nextLink = iter.next();
String parentCode = nextLink.getParent().getCode();
ourLog.info("Dropping existing parent/child link from {} -> {}", parentCode, nextCodeToAdd);
myConceptParentChildLinkDao.delete(nextLink);
iter.remove();
@ -559,42 +578,49 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
}
}
nextConceptToAdd.setParentPids(null);
nextConceptToAdd.setCodeSystemVersion(theCsv);
nextConceptToAdd = myConceptDao.save(nextConceptToAdd);
// Null out the hierarchy PIDs for this concept always. We do this because we're going to
// force a reindex, and it'll be regenerated then
conceptToAdd.setParentPids(null);
conceptToAdd.setCodeSystemVersion(theCsv);
conceptToAdd = myConceptDao.save(conceptToAdd);
Long nextConceptPid = nextConceptToAdd.getId();
Long nextConceptPid = conceptToAdd.getId();
Validate.notNull(nextConceptPid);
theCodeToConceptPid.put(nextCodeToAdd, nextConceptPid);
theStatisticsTracker.incrementUpdatedConceptCount();
// Add link to new child to the parent if this link doesn't already exist (this will be the
// case for concepts being added to an existing child concept, but won't be the case when
// we're recursively adding children)
// Add link to new child to the parent
for (TermConcept nextParentConcept : parentConcepts) {
if (nextParentConcept.getChildren().stream().noneMatch(t -> t.getChild().getCode().equals(nextCodeToAdd))) {
TermConceptParentChildLink parentLink = new TermConceptParentChildLink();
parentLink.setParent(nextParentConcept);
parentLink.setChild(nextConceptToAdd);
parentLink.setCodeSystem(theCsv);
parentLink.setRelationshipType(TermConceptParentChildLink.RelationshipTypeEnum.ISA);
nextParentConcept.getChildren().add(parentLink);
nextConceptToAdd.getParents().add(parentLink);
myConceptParentChildLinkDao.save(parentLink);
}
TermConceptParentChildLink parentLink = new TermConceptParentChildLink();
parentLink.setParent(nextParentConcept);
parentLink.setChild(conceptToAdd);
parentLink.setCodeSystem(theCsv);
parentLink.setRelationshipType(TermConceptParentChildLink.RelationshipTypeEnum.ISA);
nextParentConcept.getChildren().add(parentLink);
conceptToAdd.getParents().add(parentLink);
ourLog.info("Saving parent/child link - Parent[{}] Child[{}]", parentLink.getParent().getCode(), parentLink.getChild().getCode());
myConceptParentChildLinkDao.save(parentLink);
}
ourLog.trace("About to save parent-child links");
// Save children recursively
for (TermConceptParentChildLink nextChildConceptLink : nextConceptToAdd.getChildren()) {
for (TermConceptParentChildLink nextChildConceptLink : new ArrayList<>(childrenToAdd)) {
TermConcept nextChild = nextChildConceptLink.getChild();
Collection<String> parentCodes = nextChild.getParents().stream().map(t -> t.getParent().getCode()).collect(Collectors.toList());
addConcept(theCsv, theCodeToConceptPid, parentCodes, nextChild, theParentCodeToChildCodes, theStatisticsTracker, false);
if (nextChildConceptLink.getId() == null) {
nextChildConceptLink.setCodeSystem(theCsv);
myConceptParentChildLinkDao.save(nextChildConceptLink);
for (int i = 0; i < nextChild.getParents().size(); i++) {
if (nextChild.getParents().get(i).getId() == null) {
String parentCode = nextChild.getParents().get(i).getParent().getCode();
Long parentPid = theCodeToConceptPid.get(parentCode);
TermConcept parentConcept = myConceptDao.findById(parentPid).orElseThrow(() -> new IllegalArgumentException("Unknown parent code: " + parentCode));
nextChild.getParents().get(i).setParent(parentConcept);
}
}
Collection<String> parentCodes = nextChild.getParents().stream().map(t -> t.getParent().getCode()).collect(Collectors.toList());
addConcept(theCsv, theCodeToConceptPid, parentCodes, nextChild, theParentCodeToChildCodes, theStatisticsTracker, theAdditionSetRootConceptCodes, false);
}
}

View File

@ -28,40 +28,38 @@ import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import org.apache.commons.csv.QuoteMode;
import org.apache.commons.lang3.Validate;
import javax.annotation.Nonnull;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class CustomTerminologySet {
private final int mySize;
private final ListMultimap<TermConcept, String> myUnanchoredChildConceptsToParentCodes;
private final List<TermConcept> myRootConcepts;
/**
* Constructor for an empty object
*/
public CustomTerminologySet() {
this(0, ArrayListMultimap.create(), new ArrayList<>());
this(0, new ArrayList<>());
}
/**
* Constructor
*/
private CustomTerminologySet(int theSize, ListMultimap<TermConcept, String> theUnanchoredChildConceptsToParentCodes, Collection<TermConcept> theRootConcepts) {
this(theSize, theUnanchoredChildConceptsToParentCodes, new ArrayList<>(theRootConcepts));
}
/**
* Constructor
*/
private CustomTerminologySet(int theSize, ListMultimap<TermConcept, String> theUnanchoredChildConceptsToParentCodes, List<TermConcept> theRootConcepts) {
private CustomTerminologySet(int theSize, List<TermConcept> theRootConcepts) {
mySize = theSize;
myUnanchoredChildConceptsToParentCodes = theUnanchoredChildConceptsToParentCodes;
myRootConcepts = theRootConcepts;
}
@ -80,10 +78,6 @@ public class CustomTerminologySet {
}
public ListMultimap<TermConcept, String> getUnanchoredChildConceptsToParentCodes() {
return Multimaps.unmodifiableListMultimap(myUnanchoredChildConceptsToParentCodes);
}
public int getSize() {
return mySize;
}
@ -111,22 +105,9 @@ public class CustomTerminologySet {
return Collections.unmodifiableList(myRootConcepts);
}
public void addUnanchoredChildConcept(String theParentCode, String theCode, String theDisplay) {
Validate.notBlank(theParentCode);
Validate.notBlank(theCode);
TermConcept code = new TermConcept()
.setCode(theCode)
.setDisplay(theDisplay);
myUnanchoredChildConceptsToParentCodes.put(code, theParentCode);
}
public void validateNoCycleOrThrowInvalidRequest() {
Set<String> codes = new HashSet<>();
validateNoCycleOrThrowInvalidRequest(codes, getRootConcepts());
for (TermConcept next : myUnanchoredChildConceptsToParentCodes.keySet()) {
validateNoCycleOrThrowInvalidRequest(codes, next);
}
}
private void validateNoCycleOrThrowInvalidRequest(Set<String> theCodes, List<TermConcept> theRootConcepts) {
@ -142,25 +123,30 @@ public class CustomTerminologySet {
validateNoCycleOrThrowInvalidRequest(theCodes, next.getChildCodes());
}
public Set<String> getRootConceptCodes() {
return getRootConcepts()
.stream()
.map(TermConcept::getCode)
.collect(Collectors.toSet());
}
@Nonnull
public static CustomTerminologySet load(LoadedFileDescriptors theDescriptors, boolean theFlat) {
final Map<String, TermConcept> code2concept = new LinkedHashMap<>();
ArrayListMultimap<TermConcept, String> unanchoredChildConceptsToParentCodes = ArrayListMultimap.create();
// Concepts
IRecordHandler conceptHandler = new ConceptHandler(code2concept);
TermLoaderSvcImpl.iterateOverZipFile(theDescriptors, TermLoaderSvcImpl.CUSTOM_CONCEPTS_FILE, conceptHandler, ',', QuoteMode.NON_NUMERIC, false);
if (theFlat) {
return new CustomTerminologySet(code2concept.size(), ArrayListMultimap.create(), code2concept.values());
return new CustomTerminologySet(code2concept.size(), new ArrayList<>(code2concept.values()));
} else {
// Hierarchy
if (theDescriptors.hasFile(TermLoaderSvcImpl.CUSTOM_HIERARCHY_FILE)) {
IRecordHandler hierarchyHandler = new HierarchyHandler(code2concept, unanchoredChildConceptsToParentCodes);
IRecordHandler hierarchyHandler = new HierarchyHandler(code2concept);
TermLoaderSvcImpl.iterateOverZipFile(theDescriptors, TermLoaderSvcImpl.CUSTOM_HIERARCHY_FILE, hierarchyHandler, ',', QuoteMode.NON_NUMERIC, false);
}
@ -178,7 +164,7 @@ public class CustomTerminologySet {
}
// Sort children so they appear in the same order as they did in the concepts.csv file
nextConcept.getChildren().sort((o1,o2)->{
nextConcept.getChildren().sort((o1, o2) -> {
String code1 = o1.getChild().getCode();
String code2 = o2.getChild().getCode();
int order1 = codesInOrder.get(code1);
@ -188,7 +174,7 @@ public class CustomTerminologySet {
}
return new CustomTerminologySet(code2concept.size(), unanchoredChildConceptsToParentCodes, rootConcepts);
return new CustomTerminologySet(code2concept.size(), rootConcepts);
}
}

View File

@ -24,7 +24,6 @@ import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.jpa.term.IRecordHandler;
import ca.uhn.fhir.util.ValidateUtil;
import com.google.common.collect.ArrayListMultimap;
import org.apache.commons.csv.CSVRecord;
import java.util.Map;
@ -37,11 +36,9 @@ public class HierarchyHandler implements IRecordHandler {
public static final String PARENT = "PARENT";
public static final String CHILD = "CHILD";
private final Map<String, TermConcept> myCode2Concept;
private final ArrayListMultimap<TermConcept, String> myUnanchoredChildConceptsToParentCodes;
public HierarchyHandler(Map<String, TermConcept> theCode2concept, ArrayListMultimap<TermConcept, String> theunanchoredChildConceptsToParentCodes) {
public HierarchyHandler(Map<String, TermConcept> theCode2concept) {
myCode2Concept = theCode2concept;
myUnanchoredChildConceptsToParentCodes = theunanchoredChildConceptsToParentCodes;
}
@Override
@ -51,14 +48,12 @@ public class HierarchyHandler implements IRecordHandler {
if (isNotBlank(parent) && isNotBlank(child)) {
TermConcept childConcept = myCode2Concept.get(child);
ValidateUtil.isNotNullOrThrowUnprocessableEntity(childConcept, "Child code %s not found", child);
ValidateUtil.isNotNullOrThrowUnprocessableEntity(childConcept, "Child code %s not found in file", child);
TermConcept parentConcept = myCode2Concept.get(parent);
if (parentConcept == null) {
myUnanchoredChildConceptsToParentCodes.put(childConcept, parent);
} else {
parentConcept.addChild(childConcept, TermConceptParentChildLink.RelationshipTypeEnum.ISA);
}
ValidateUtil.isNotNullOrThrowUnprocessableEntity(parentConcept, "Parent code %s not found in file", child);
parentConcept.addChild(childConcept, TermConceptParentChildLink.RelationshipTypeEnum.ISA);
}
}
}

View File

@ -2,8 +2,6 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
@ -12,23 +10,26 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.AfterClass;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.TestPropertySource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
public class TerminologySvcDeltaR4Test extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcDeltaR4Test.class);
@ -114,8 +115,9 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test {
});
delta = new CustomTerminologySet();
delta.addUnanchoredChildConcept("RootA", "ChildAA", "Child AA");
delta.addUnanchoredChildConcept("RootA", "ChildAB", "Child AB");
TermConcept root = delta.addRootConcept("RootA", "Root A");
root.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildAA").setDisplay("Child AA");
root.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildAB").setDisplay("Child AB");
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta);
assertHierarchyContains(
"RootA seq=1",
@ -135,25 +137,29 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test {
delta = new CustomTerminologySet();
delta.addRootConcept("RootA", "Root A")
.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildAA").setDisplay("Child AA");
.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildAA").setDisplay("Child AA")
.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildAAA").setDisplay("Child AAA");
delta.addRootConcept("RootB", "Root B");
outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta);
assertHierarchyContains(
"RootA seq=1",
" ChildAA seq=1",
" ChildAAA seq=1",
"RootB seq=2"
);
assertEquals(3, outcome.getUpdatedConceptCount());
assertEquals(4, outcome.getUpdatedConceptCount());
delta = new CustomTerminologySet();
delta.addUnanchoredChildConcept("RootB", "ChildAA", "Child AA");
delta.addRootConcept("RootB", "Root B")
.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildAA").setDisplay("Child AA");
outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta);
assertEquals(1, outcome.getUpdatedConceptCount());
assertHierarchyContains(
"RootA seq=1",
"RootB seq=2",
" ChildAA seq=1"
" ChildAA seq=1",
" ChildAAA seq=1"
);
assertEquals(2, outcome.getUpdatedConceptCount());
}
@ -166,11 +172,6 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test {
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
myCodeSystemDao.create(cs);
CodeSystem delta = new CodeSystem();
delta
.addConcept()
.setCode("codeA")
.setDisplay("displayA");
try {
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", new CustomTerminologySet());
fail();
@ -180,6 +181,44 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test {
}
@Test
public void testAddChildToExistingChild() {
CustomTerminologySet set;
// Create not-present
CodeSystem cs = new CodeSystem();
cs.setUrl("http://foo");
cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT);
myCodeSystemDao.create(cs);
// Add parent with 1 child
set = new CustomTerminologySet();
set.addRootConcept("ParentA", "Parent A")
.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildA").setDisplay("Child A");
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", set);
// Check so far
assertHierarchyContains(
"ParentA seq=1",
" ChildA seq=1"
);
// Add sub-child to existing child
ourLog.info("*** Adding child to existing child");
set = new CustomTerminologySet();
set.addRootConcept("ChildA", "Child A")
.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("ChildAA").setDisplay("Child AA");
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", set);
// Check so far
assertHierarchyContains(
"ParentA seq=1",
" ChildA seq=1",
" ChildAA seq=1"
);
}
@Test
public void testAddWithoutPreExistingCodeSystem() {
@ -229,27 +268,6 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test {
assertEquals("CODEA1", myTermSvc.lookupCode(myFhirCtx, "http://foo", "codea").getCodeDisplay());
}
@Test
public void testAddUnanchoredWithUnknownParent() {
createNotPresentCodeSystem();
// Add root code
CustomTerminologySet delta = new CustomTerminologySet();
delta.addRootConcept("CodeA", "Code A");
UploadStatistics outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", delta);
assertEquals(1, outcome.getUpdatedConceptCount());
// Try to add child to nonexistent root code
delta = new CustomTerminologySet();
delta.addUnanchoredChildConcept("CodeB", "CodeBB", "Code BB");
try {
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Unable to add code \"CodeBB\" to unknown parent: CodeB"));
}
}
@Test
public void testAddRelocateHierarchy() {
createNotPresentCodeSystem();
@ -280,9 +298,11 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test {
// Move a single child code to a new spot and make sure the hierarchy comes along
// for the ride..
delta = new CustomTerminologySet();
delta.addUnanchoredChildConcept("CodeB", "CodeAA", "Code AA");
delta
.addRootConcept("CodeB", "Code B")
.addChild(TermConceptParentChildLink.RelationshipTypeEnum.ISA).setCode("CodeAA").setDisplay("Code AA");
outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", delta);
assertEquals(3, outcome.getUpdatedConceptCount());
assertEquals(2, outcome.getUpdatedConceptCount());
assertHierarchyContains(
"CodeA seq=1",
"CodeB seq=2",
@ -431,8 +451,6 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test {
}
private ValueSet expandNotPresentCodeSystem() {
ValueSet vs = new ValueSet();
vs.setUrl("http://foo/vs");