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:
parent
4d523aa38c
commit
ba0048aade
|
@ -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:
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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<>();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue