Add ITermConceptClientMappingSvc interface to represent terminology t… (#5410)
* Add ITermConceptClientMappingSvc interface to represent terminology translate functions * Add javadoc --------- Co-authored-by: juan.marchionatto <juan.marchionatto@smilecdr.com>
This commit is contained in:
parent
a7a446903f
commit
c89fc46863
|
@ -0,0 +1,420 @@
|
||||||
|
package ca.uhn.fhir.jpa.term;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.support.TranslateConceptResult;
|
||||||
|
import ca.uhn.fhir.context.support.TranslateConceptResults;
|
||||||
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
|
import ca.uhn.fhir.jpa.api.model.TranslationQuery;
|
||||||
|
import ca.uhn.fhir.jpa.api.model.TranslationRequest;
|
||||||
|
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapDao;
|
||||||
|
import ca.uhn.fhir.jpa.entity.TermConceptMap;
|
||||||
|
import ca.uhn.fhir.jpa.entity.TermConceptMapGroup;
|
||||||
|
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement;
|
||||||
|
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget;
|
||||||
|
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
||||||
|
import ca.uhn.fhir.jpa.term.api.ITermConceptClientMappingSvc;
|
||||||
|
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||||
|
import ca.uhn.fhir.jpa.util.ScrollableResultsIterator;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.hibernate.ScrollMode;
|
||||||
|
import org.hibernate.ScrollableResults;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.hl7.fhir.r4.model.Coding;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.PersistenceContext;
|
||||||
|
import javax.persistence.PersistenceContextType;
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
|
import javax.persistence.criteria.Join;
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
|
import javax.persistence.criteria.Root;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
public class TermConceptClientMappingSvcImpl implements ITermConceptClientMappingSvc {
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(TermConceptClientMappingSvcImpl.class);
|
||||||
|
|
||||||
|
private final int myFetchSize = TermReadSvcImpl.DEFAULT_FETCH_SIZE;
|
||||||
|
|
||||||
|
protected static boolean ourLastResultsFromTranslationCache; // For testing.
|
||||||
|
protected static boolean ourLastResultsFromTranslationWithReverseCache; // For testing.
|
||||||
|
|
||||||
|
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||||
|
protected EntityManager myEntityManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected FhirContext myContext;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected MemoryCacheService myMemoryCacheService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected IIdHelperService<JpaPid> myIdHelperService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected ITermConceptMapDao myConceptMapDao;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(propagation = Propagation.REQUIRED)
|
||||||
|
public TranslateConceptResults translate(TranslationRequest theTranslationRequest) {
|
||||||
|
TranslateConceptResults retVal = new TranslateConceptResults();
|
||||||
|
|
||||||
|
CriteriaBuilder criteriaBuilder = myEntityManager.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<TermConceptMapGroupElementTarget> query =
|
||||||
|
criteriaBuilder.createQuery(TermConceptMapGroupElementTarget.class);
|
||||||
|
Root<TermConceptMapGroupElementTarget> root = query.from(TermConceptMapGroupElementTarget.class);
|
||||||
|
|
||||||
|
Join<TermConceptMapGroupElementTarget, TermConceptMapGroupElement> elementJoin =
|
||||||
|
root.join("myConceptMapGroupElement");
|
||||||
|
Join<TermConceptMapGroupElement, TermConceptMapGroup> groupJoin = elementJoin.join("myConceptMapGroup");
|
||||||
|
Join<TermConceptMapGroup, TermConceptMap> conceptMapJoin = groupJoin.join("myConceptMap");
|
||||||
|
|
||||||
|
List<TranslationQuery> translationQueries = theTranslationRequest.getTranslationQueries();
|
||||||
|
List<TranslateConceptResult> cachedTargets;
|
||||||
|
ArrayList<Predicate> predicates;
|
||||||
|
Coding coding;
|
||||||
|
|
||||||
|
// -- get the latest ConceptMapVersion if theTranslationRequest has ConceptMap url but no ConceptMap version
|
||||||
|
String latestConceptMapVersion = null;
|
||||||
|
if (theTranslationRequest.hasUrl() && !theTranslationRequest.hasConceptMapVersion())
|
||||||
|
latestConceptMapVersion = getLatestConceptMapVersion(theTranslationRequest);
|
||||||
|
|
||||||
|
for (TranslationQuery translationQuery : translationQueries) {
|
||||||
|
cachedTargets = myMemoryCacheService.getIfPresent(
|
||||||
|
MemoryCacheService.CacheEnum.CONCEPT_TRANSLATION, translationQuery);
|
||||||
|
if (cachedTargets == null) {
|
||||||
|
final List<TranslateConceptResult> targets = new ArrayList<>();
|
||||||
|
|
||||||
|
predicates = new ArrayList<>();
|
||||||
|
|
||||||
|
coding = translationQuery.getCoding();
|
||||||
|
if (coding.hasCode()) {
|
||||||
|
predicates.add(criteriaBuilder.equal(elementJoin.get("myCode"), coding.getCode()));
|
||||||
|
} else {
|
||||||
|
throw new InvalidRequestException(
|
||||||
|
Msg.code(842) + "A code must be provided for translation to occur.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coding.hasSystem()) {
|
||||||
|
predicates.add(criteriaBuilder.equal(groupJoin.get("mySource"), coding.getSystem()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coding.hasVersion()) {
|
||||||
|
predicates.add(criteriaBuilder.equal(groupJoin.get("mySourceVersion"), coding.getVersion()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationQuery.hasTargetSystem()) {
|
||||||
|
predicates.add(
|
||||||
|
criteriaBuilder.equal(groupJoin.get("myTarget"), translationQuery.getTargetSystem()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationQuery.hasUrl()) {
|
||||||
|
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myUrl"), translationQuery.getUrl()));
|
||||||
|
if (translationQuery.hasConceptMapVersion()) {
|
||||||
|
// both url and conceptMapVersion
|
||||||
|
predicates.add(criteriaBuilder.equal(
|
||||||
|
conceptMapJoin.get("myVersion"), translationQuery.getConceptMapVersion()));
|
||||||
|
} else {
|
||||||
|
if (StringUtils.isNotBlank(latestConceptMapVersion)) {
|
||||||
|
// only url and use latestConceptMapVersion
|
||||||
|
predicates.add(
|
||||||
|
criteriaBuilder.equal(conceptMapJoin.get("myVersion"), latestConceptMapVersion));
|
||||||
|
} else {
|
||||||
|
predicates.add(criteriaBuilder.isNull(conceptMapJoin.get("myVersion")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationQuery.hasSource()) {
|
||||||
|
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("mySource"), translationQuery.getSource()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationQuery.hasTarget()) {
|
||||||
|
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myTarget"), translationQuery.getTarget()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationQuery.hasResourceId()) {
|
||||||
|
IIdType resourceId = translationQuery.getResourceId();
|
||||||
|
JpaPid resourcePid =
|
||||||
|
myIdHelperService.getPidOrThrowException(RequestPartitionId.defaultPartition(), resourceId);
|
||||||
|
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myResourcePid"), resourcePid.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Predicate outerPredicate = criteriaBuilder.and(predicates.toArray(new Predicate[0]));
|
||||||
|
query.where(outerPredicate);
|
||||||
|
|
||||||
|
// Use scrollable results.
|
||||||
|
final TypedQuery<TermConceptMapGroupElementTarget> typedQuery =
|
||||||
|
myEntityManager.createQuery(query.select(root));
|
||||||
|
org.hibernate.query.Query<TermConceptMapGroupElementTarget> hibernateQuery =
|
||||||
|
(org.hibernate.query.Query<TermConceptMapGroupElementTarget>) typedQuery;
|
||||||
|
hibernateQuery.setFetchSize(myFetchSize);
|
||||||
|
ScrollableResults scrollableResults = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY);
|
||||||
|
try (ScrollableResultsIterator<TermConceptMapGroupElementTarget> scrollableResultsIterator =
|
||||||
|
new ScrollableResultsIterator<>(scrollableResults)) {
|
||||||
|
|
||||||
|
Set<TermConceptMapGroupElementTarget> matches = new HashSet<>();
|
||||||
|
while (scrollableResultsIterator.hasNext()) {
|
||||||
|
TermConceptMapGroupElementTarget next = scrollableResultsIterator.next();
|
||||||
|
if (matches.add(next)) {
|
||||||
|
|
||||||
|
TranslateConceptResult translationMatch = new TranslateConceptResult();
|
||||||
|
if (next.getEquivalence() != null) {
|
||||||
|
translationMatch.setEquivalence(
|
||||||
|
next.getEquivalence().toCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
translationMatch.setCode(next.getCode());
|
||||||
|
translationMatch.setSystem(next.getSystem());
|
||||||
|
translationMatch.setSystemVersion(next.getSystemVersion());
|
||||||
|
translationMatch.setDisplay(next.getDisplay());
|
||||||
|
translationMatch.setValueSet(next.getValueSet());
|
||||||
|
translationMatch.setSystemVersion(next.getSystemVersion());
|
||||||
|
translationMatch.setConceptMapUrl(next.getConceptMapUrl());
|
||||||
|
|
||||||
|
targets.add(translationMatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ourLastResultsFromTranslationCache = false; // For testing.
|
||||||
|
myMemoryCacheService.put(MemoryCacheService.CacheEnum.CONCEPT_TRANSLATION, translationQuery, targets);
|
||||||
|
retVal.getResults().addAll(targets);
|
||||||
|
} else {
|
||||||
|
ourLastResultsFromTranslationCache = true; // For testing.
|
||||||
|
retVal.getResults().addAll(cachedTargets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTranslationResult(retVal);
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(propagation = Propagation.REQUIRED)
|
||||||
|
public TranslateConceptResults translateWithReverse(TranslationRequest theTranslationRequest) {
|
||||||
|
TranslateConceptResults retVal = new TranslateConceptResults();
|
||||||
|
|
||||||
|
CriteriaBuilder criteriaBuilder = myEntityManager.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<TermConceptMapGroupElement> query = criteriaBuilder.createQuery(TermConceptMapGroupElement.class);
|
||||||
|
Root<TermConceptMapGroupElement> root = query.from(TermConceptMapGroupElement.class);
|
||||||
|
|
||||||
|
Join<TermConceptMapGroupElement, TermConceptMapGroupElementTarget> targetJoin =
|
||||||
|
root.join("myConceptMapGroupElementTargets");
|
||||||
|
Join<TermConceptMapGroupElement, TermConceptMapGroup> groupJoin = root.join("myConceptMapGroup");
|
||||||
|
Join<TermConceptMapGroup, TermConceptMap> conceptMapJoin = groupJoin.join("myConceptMap");
|
||||||
|
|
||||||
|
List<TranslationQuery> translationQueries = theTranslationRequest.getTranslationQueries();
|
||||||
|
List<TranslateConceptResult> cachedElements;
|
||||||
|
ArrayList<Predicate> predicates;
|
||||||
|
Coding coding;
|
||||||
|
|
||||||
|
// -- get the latest ConceptMapVersion if theTranslationRequest has ConceptMap url but no ConceptMap version
|
||||||
|
String latestConceptMapVersion = null;
|
||||||
|
if (theTranslationRequest.hasUrl() && !theTranslationRequest.hasConceptMapVersion())
|
||||||
|
latestConceptMapVersion = getLatestConceptMapVersion(theTranslationRequest);
|
||||||
|
|
||||||
|
for (TranslationQuery translationQuery : translationQueries) {
|
||||||
|
cachedElements = myMemoryCacheService.getIfPresent(
|
||||||
|
MemoryCacheService.CacheEnum.CONCEPT_TRANSLATION_REVERSE, translationQuery);
|
||||||
|
if (cachedElements == null) {
|
||||||
|
final List<TranslateConceptResult> elements = new ArrayList<>();
|
||||||
|
|
||||||
|
predicates = new ArrayList<>();
|
||||||
|
|
||||||
|
coding = translationQuery.getCoding();
|
||||||
|
String targetCode;
|
||||||
|
String targetCodeSystem = null;
|
||||||
|
if (coding.hasCode()) {
|
||||||
|
predicates.add(criteriaBuilder.equal(targetJoin.get("myCode"), coding.getCode()));
|
||||||
|
targetCode = coding.getCode();
|
||||||
|
} else {
|
||||||
|
throw new InvalidRequestException(
|
||||||
|
Msg.code(843) + "A code must be provided for translation to occur.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coding.hasSystem()) {
|
||||||
|
predicates.add(criteriaBuilder.equal(groupJoin.get("myTarget"), coding.getSystem()));
|
||||||
|
targetCodeSystem = coding.getSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coding.hasVersion()) {
|
||||||
|
predicates.add(criteriaBuilder.equal(groupJoin.get("myTargetVersion"), coding.getVersion()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationQuery.hasUrl()) {
|
||||||
|
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myUrl"), translationQuery.getUrl()));
|
||||||
|
if (translationQuery.hasConceptMapVersion()) {
|
||||||
|
// both url and conceptMapVersion
|
||||||
|
predicates.add(criteriaBuilder.equal(
|
||||||
|
conceptMapJoin.get("myVersion"), translationQuery.getConceptMapVersion()));
|
||||||
|
} else {
|
||||||
|
if (StringUtils.isNotBlank(latestConceptMapVersion)) {
|
||||||
|
// only url and use latestConceptMapVersion
|
||||||
|
predicates.add(
|
||||||
|
criteriaBuilder.equal(conceptMapJoin.get("myVersion"), latestConceptMapVersion));
|
||||||
|
} else {
|
||||||
|
predicates.add(criteriaBuilder.isNull(conceptMapJoin.get("myVersion")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationQuery.hasTargetSystem()) {
|
||||||
|
predicates.add(
|
||||||
|
criteriaBuilder.equal(groupJoin.get("mySource"), translationQuery.getTargetSystem()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationQuery.hasSource()) {
|
||||||
|
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myTarget"), translationQuery.getSource()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationQuery.hasTarget()) {
|
||||||
|
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("mySource"), translationQuery.getTarget()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationQuery.hasResourceId()) {
|
||||||
|
IIdType resourceId = translationQuery.getResourceId();
|
||||||
|
JpaPid resourcePid =
|
||||||
|
myIdHelperService.getPidOrThrowException(RequestPartitionId.defaultPartition(), resourceId);
|
||||||
|
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myResourcePid"), resourcePid.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Predicate outerPredicate = criteriaBuilder.and(predicates.toArray(new Predicate[0]));
|
||||||
|
query.where(outerPredicate);
|
||||||
|
|
||||||
|
// Use scrollable results.
|
||||||
|
final TypedQuery<TermConceptMapGroupElement> typedQuery =
|
||||||
|
myEntityManager.createQuery(query.select(root));
|
||||||
|
org.hibernate.query.Query<TermConceptMapGroupElement> hibernateQuery =
|
||||||
|
(org.hibernate.query.Query<TermConceptMapGroupElement>) typedQuery;
|
||||||
|
hibernateQuery.setFetchSize(myFetchSize);
|
||||||
|
ScrollableResults scrollableResults = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY);
|
||||||
|
try (ScrollableResultsIterator<TermConceptMapGroupElement> scrollableResultsIterator =
|
||||||
|
new ScrollableResultsIterator<>(scrollableResults)) {
|
||||||
|
|
||||||
|
Set<TermConceptMapGroupElementTarget> matches = new HashSet<>();
|
||||||
|
while (scrollableResultsIterator.hasNext()) {
|
||||||
|
TermConceptMapGroupElement nextElement = scrollableResultsIterator.next();
|
||||||
|
|
||||||
|
/* TODO: The invocation of the size() below does not seem to be necessary but for some reason,
|
||||||
|
* but removing it causes tests in TerminologySvcImplR4Test to fail. We use the outcome
|
||||||
|
* in a trace log to avoid ErrorProne flagging an unused return value.
|
||||||
|
*/
|
||||||
|
int size =
|
||||||
|
nextElement.getConceptMapGroupElementTargets().size();
|
||||||
|
ourLog.trace("Have {} targets", size);
|
||||||
|
|
||||||
|
myEntityManager.detach(nextElement);
|
||||||
|
|
||||||
|
if (isNotBlank(targetCode)) {
|
||||||
|
for (TermConceptMapGroupElementTarget next :
|
||||||
|
nextElement.getConceptMapGroupElementTargets()) {
|
||||||
|
if (matches.add(next)) {
|
||||||
|
if (isBlank(targetCodeSystem)
|
||||||
|
|| StringUtils.equals(targetCodeSystem, next.getSystem())) {
|
||||||
|
if (StringUtils.equals(targetCode, next.getCode())) {
|
||||||
|
TranslateConceptResult translationMatch = new TranslateConceptResult();
|
||||||
|
translationMatch.setCode(nextElement.getCode());
|
||||||
|
translationMatch.setSystem(nextElement.getSystem());
|
||||||
|
translationMatch.setSystemVersion(nextElement.getSystemVersion());
|
||||||
|
translationMatch.setDisplay(nextElement.getDisplay());
|
||||||
|
translationMatch.setValueSet(nextElement.getValueSet());
|
||||||
|
translationMatch.setSystemVersion(nextElement.getSystemVersion());
|
||||||
|
translationMatch.setConceptMapUrl(nextElement.getConceptMapUrl());
|
||||||
|
if (next.getEquivalence() != null) {
|
||||||
|
translationMatch.setEquivalence(
|
||||||
|
next.getEquivalence().toCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alreadyContainsMapping(elements, translationMatch)
|
||||||
|
|| alreadyContainsMapping(retVal.getResults(), translationMatch)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.add(translationMatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ourLastResultsFromTranslationWithReverseCache = false; // For testing.
|
||||||
|
myMemoryCacheService.put(
|
||||||
|
MemoryCacheService.CacheEnum.CONCEPT_TRANSLATION_REVERSE, translationQuery, elements);
|
||||||
|
retVal.getResults().addAll(elements);
|
||||||
|
} else {
|
||||||
|
ourLastResultsFromTranslationWithReverseCache = true; // For testing.
|
||||||
|
retVal.getResults().addAll(cachedElements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTranslationResult(retVal);
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FhirContext getFhirContext() {
|
||||||
|
return myContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for the translate operation with url and without
|
||||||
|
// conceptMapVersion, find the latest conecptMapVersion
|
||||||
|
private String getLatestConceptMapVersion(TranslationRequest theTranslationRequest) {
|
||||||
|
|
||||||
|
Pageable page = PageRequest.of(0, 1);
|
||||||
|
List<TermConceptMap> theConceptMapList = myConceptMapDao.getTermConceptMapEntitiesByUrlOrderByMostRecentUpdate(
|
||||||
|
page, theTranslationRequest.getUrl());
|
||||||
|
if (!theConceptMapList.isEmpty()) {
|
||||||
|
return theConceptMapList.get(0).getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildTranslationResult(TranslateConceptResults theTranslationResult) {
|
||||||
|
|
||||||
|
String msg;
|
||||||
|
if (theTranslationResult.getResults().isEmpty()) {
|
||||||
|
theTranslationResult.setResult(false);
|
||||||
|
msg = myContext.getLocalizer().getMessage(TermConceptMappingSvcImpl.class, "noMatchesFound");
|
||||||
|
theTranslationResult.setMessage(msg);
|
||||||
|
} else {
|
||||||
|
theTranslationResult.setResult(true);
|
||||||
|
msg = myContext.getLocalizer().getMessage(TermConceptMappingSvcImpl.class, "matchesFound");
|
||||||
|
theTranslationResult.setMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean alreadyContainsMapping(
|
||||||
|
List<TranslateConceptResult> elements, TranslateConceptResult translationMatch) {
|
||||||
|
for (TranslateConceptResult nextExistingElement : elements) {
|
||||||
|
if (StringUtils.equals(nextExistingElement.getSystem(), translationMatch.getSystem())) {
|
||||||
|
if (StringUtils.equals(nextExistingElement.getSystemVersion(), translationMatch.getSystemVersion())) {
|
||||||
|
if (StringUtils.equals(nextExistingElement.getCode(), translationMatch.getCode())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,15 +19,10 @@
|
||||||
*/
|
*/
|
||||||
package ca.uhn.fhir.jpa.term;
|
package ca.uhn.fhir.jpa.term;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.context.support.TranslateConceptResult;
|
import ca.uhn.fhir.context.support.TranslateConceptResult;
|
||||||
import ca.uhn.fhir.context.support.TranslateConceptResults;
|
import ca.uhn.fhir.context.support.TranslateConceptResults;
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|
||||||
import ca.uhn.fhir.jpa.api.model.TranslationQuery;
|
|
||||||
import ca.uhn.fhir.jpa.api.model.TranslationRequest;
|
import ca.uhn.fhir.jpa.api.model.TranslationRequest;
|
||||||
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapDao;
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupDao;
|
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementDao;
|
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementDao;
|
||||||
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementTargetDao;
|
import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementTargetDao;
|
||||||
|
@ -35,21 +30,13 @@ import ca.uhn.fhir.jpa.entity.TermConceptMap;
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptMapGroup;
|
import ca.uhn.fhir.jpa.entity.TermConceptMapGroup;
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement;
|
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement;
|
||||||
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget;
|
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget;
|
||||||
import ca.uhn.fhir.jpa.model.dao.JpaPid;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.term.api.ITermConceptMappingSvc;
|
import ca.uhn.fhir.jpa.term.api.ITermConceptMappingSvc;
|
||||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
|
||||||
import ca.uhn.fhir.jpa.util.ScrollableResultsIterator;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.util.ValidateUtil;
|
import ca.uhn.fhir.util.ValidateUtil;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.hibernate.ScrollMode;
|
|
||||||
import org.hibernate.ScrollableResults;
|
|
||||||
import org.hl7.fhir.exceptions.FHIRException;
|
import org.hl7.fhir.exceptions.FHIRException;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
|
||||||
import org.hl7.fhir.r4.model.BooleanType;
|
import org.hl7.fhir.r4.model.BooleanType;
|
||||||
import org.hl7.fhir.r4.model.CodeType;
|
import org.hl7.fhir.r4.model.CodeType;
|
||||||
import org.hl7.fhir.r4.model.Coding;
|
import org.hl7.fhir.r4.model.Coding;
|
||||||
|
@ -61,39 +48,17 @@ import org.hl7.fhir.r4.model.UriType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.PageRequest;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.PersistenceContext;
|
|
||||||
import javax.persistence.PersistenceContextType;
|
|
||||||
import javax.persistence.TypedQuery;
|
|
||||||
import javax.persistence.criteria.CriteriaBuilder;
|
|
||||||
import javax.persistence.criteria.CriteriaQuery;
|
|
||||||
import javax.persistence.criteria.Join;
|
|
||||||
import javax.persistence.criteria.Predicate;
|
|
||||||
import javax.persistence.criteria.Root;
|
|
||||||
|
|
||||||
import static ca.uhn.fhir.jpa.term.TermReadSvcImpl.isPlaceholder;
|
import static ca.uhn.fhir.jpa.term.TermReadSvcImpl.isPlaceholder;
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
public class TermConceptMappingSvcImpl implements ITermConceptMappingSvc {
|
public class TermConceptMappingSvcImpl extends TermConceptClientMappingSvcImpl implements ITermConceptMappingSvc {
|
||||||
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(TermConceptMappingSvcImpl.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(TermConceptMappingSvcImpl.class);
|
||||||
private static boolean ourLastResultsFromTranslationCache; // For testing.
|
|
||||||
private static boolean ourLastResultsFromTranslationWithReverseCache; // For testing.
|
|
||||||
private final int myFetchSize = TermReadSvcImpl.DEFAULT_FETCH_SIZE;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected ITermConceptMapDao myConceptMapDao;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
protected ITermConceptMapGroupDao myConceptMapGroupDao;
|
protected ITermConceptMapGroupDao myConceptMapGroupDao;
|
||||||
|
@ -104,29 +69,12 @@ public class TermConceptMappingSvcImpl implements ITermConceptMappingSvc {
|
||||||
@Autowired
|
@Autowired
|
||||||
protected ITermConceptMapGroupElementTargetDao myConceptMapGroupElementTargetDao;
|
protected ITermConceptMapGroupElementTargetDao myConceptMapGroupElementTargetDao;
|
||||||
|
|
||||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
|
||||||
protected EntityManager myEntityManager;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private FhirContext myContext;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private MemoryCacheService myMemoryCacheService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IIdHelperService<JpaPid> myIdHelperService;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void deleteConceptMapAndChildren(ResourceTable theResourceTable) {
|
public void deleteConceptMapAndChildren(ResourceTable theResourceTable) {
|
||||||
deleteConceptMap(theResourceTable);
|
deleteConceptMap(theResourceTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public FhirContext getFhirContext() {
|
|
||||||
return myContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public TranslateConceptResults translateConcept(TranslateCodeRequest theRequest) {
|
public TranslateConceptResults translateConcept(TranslateCodeRequest theRequest) {
|
||||||
|
@ -205,7 +153,7 @@ public class TermConceptMappingSvcImpl implements ITermConceptMappingSvc {
|
||||||
optionalExistingTermConceptMapByUrl =
|
optionalExistingTermConceptMapByUrl =
|
||||||
myConceptMapDao.findTermConceptMapByUrlAndVersion(conceptMapUrl, conceptMapVersion);
|
myConceptMapDao.findTermConceptMapByUrlAndVersion(conceptMapUrl, conceptMapVersion);
|
||||||
}
|
}
|
||||||
if (!optionalExistingTermConceptMapByUrl.isPresent()) {
|
if (optionalExistingTermConceptMapByUrl.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
if (isNotBlank(source)) {
|
if (isNotBlank(source)) {
|
||||||
termConceptMap.setSource(source);
|
termConceptMap.setSource(source);
|
||||||
|
@ -328,320 +276,6 @@ public class TermConceptMappingSvcImpl implements ITermConceptMappingSvc {
|
||||||
theConceptMap.getIdElement().toVersionless().getValueAsString());
|
theConceptMap.getIdElement().toVersionless().getValueAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(propagation = Propagation.REQUIRED)
|
|
||||||
public TranslateConceptResults translate(TranslationRequest theTranslationRequest) {
|
|
||||||
TranslateConceptResults retVal = new TranslateConceptResults();
|
|
||||||
|
|
||||||
CriteriaBuilder criteriaBuilder = myEntityManager.getCriteriaBuilder();
|
|
||||||
CriteriaQuery<TermConceptMapGroupElementTarget> query =
|
|
||||||
criteriaBuilder.createQuery(TermConceptMapGroupElementTarget.class);
|
|
||||||
Root<TermConceptMapGroupElementTarget> root = query.from(TermConceptMapGroupElementTarget.class);
|
|
||||||
|
|
||||||
Join<TermConceptMapGroupElementTarget, TermConceptMapGroupElement> elementJoin =
|
|
||||||
root.join("myConceptMapGroupElement");
|
|
||||||
Join<TermConceptMapGroupElement, TermConceptMapGroup> groupJoin = elementJoin.join("myConceptMapGroup");
|
|
||||||
Join<TermConceptMapGroup, TermConceptMap> conceptMapJoin = groupJoin.join("myConceptMap");
|
|
||||||
|
|
||||||
List<TranslationQuery> translationQueries = theTranslationRequest.getTranslationQueries();
|
|
||||||
List<TranslateConceptResult> cachedTargets;
|
|
||||||
ArrayList<Predicate> predicates;
|
|
||||||
Coding coding;
|
|
||||||
|
|
||||||
// -- get the latest ConceptMapVersion if theTranslationRequest has ConceptMap url but no ConceptMap version
|
|
||||||
String latestConceptMapVersion = null;
|
|
||||||
if (theTranslationRequest.hasUrl() && !theTranslationRequest.hasConceptMapVersion())
|
|
||||||
latestConceptMapVersion = getLatestConceptMapVersion(theTranslationRequest);
|
|
||||||
|
|
||||||
for (TranslationQuery translationQuery : translationQueries) {
|
|
||||||
cachedTargets = myMemoryCacheService.getIfPresent(
|
|
||||||
MemoryCacheService.CacheEnum.CONCEPT_TRANSLATION, translationQuery);
|
|
||||||
if (cachedTargets == null) {
|
|
||||||
final List<TranslateConceptResult> targets = new ArrayList<>();
|
|
||||||
|
|
||||||
predicates = new ArrayList<>();
|
|
||||||
|
|
||||||
coding = translationQuery.getCoding();
|
|
||||||
if (coding.hasCode()) {
|
|
||||||
predicates.add(criteriaBuilder.equal(elementJoin.get("myCode"), coding.getCode()));
|
|
||||||
} else {
|
|
||||||
throw new InvalidRequestException(
|
|
||||||
Msg.code(842) + "A code must be provided for translation to occur.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (coding.hasSystem()) {
|
|
||||||
predicates.add(criteriaBuilder.equal(groupJoin.get("mySource"), coding.getSystem()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (coding.hasVersion()) {
|
|
||||||
predicates.add(criteriaBuilder.equal(groupJoin.get("mySourceVersion"), coding.getVersion()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translationQuery.hasTargetSystem()) {
|
|
||||||
predicates.add(
|
|
||||||
criteriaBuilder.equal(groupJoin.get("myTarget"), translationQuery.getTargetSystem()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translationQuery.hasUrl()) {
|
|
||||||
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myUrl"), translationQuery.getUrl()));
|
|
||||||
if (translationQuery.hasConceptMapVersion()) {
|
|
||||||
// both url and conceptMapVersion
|
|
||||||
predicates.add(criteriaBuilder.equal(
|
|
||||||
conceptMapJoin.get("myVersion"), translationQuery.getConceptMapVersion()));
|
|
||||||
} else {
|
|
||||||
if (StringUtils.isNotBlank(latestConceptMapVersion)) {
|
|
||||||
// only url and use latestConceptMapVersion
|
|
||||||
predicates.add(
|
|
||||||
criteriaBuilder.equal(conceptMapJoin.get("myVersion"), latestConceptMapVersion));
|
|
||||||
} else {
|
|
||||||
predicates.add(criteriaBuilder.isNull(conceptMapJoin.get("myVersion")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translationQuery.hasSource()) {
|
|
||||||
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("mySource"), translationQuery.getSource()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translationQuery.hasTarget()) {
|
|
||||||
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myTarget"), translationQuery.getTarget()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translationQuery.hasResourceId()) {
|
|
||||||
IIdType resourceId = translationQuery.getResourceId();
|
|
||||||
JpaPid resourcePid =
|
|
||||||
myIdHelperService.getPidOrThrowException(RequestPartitionId.defaultPartition(), resourceId);
|
|
||||||
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myResourcePid"), resourcePid.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate outerPredicate = criteriaBuilder.and(predicates.toArray(new Predicate[0]));
|
|
||||||
query.where(outerPredicate);
|
|
||||||
|
|
||||||
// Use scrollable results.
|
|
||||||
final TypedQuery<TermConceptMapGroupElementTarget> typedQuery =
|
|
||||||
myEntityManager.createQuery(query.select(root));
|
|
||||||
org.hibernate.query.Query<TermConceptMapGroupElementTarget> hibernateQuery =
|
|
||||||
(org.hibernate.query.Query<TermConceptMapGroupElementTarget>) typedQuery;
|
|
||||||
hibernateQuery.setFetchSize(myFetchSize);
|
|
||||||
ScrollableResults scrollableResults = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY);
|
|
||||||
try (ScrollableResultsIterator<TermConceptMapGroupElementTarget> scrollableResultsIterator =
|
|
||||||
new ScrollableResultsIterator<>(scrollableResults)) {
|
|
||||||
|
|
||||||
Set<TermConceptMapGroupElementTarget> matches = new HashSet<>();
|
|
||||||
while (scrollableResultsIterator.hasNext()) {
|
|
||||||
TermConceptMapGroupElementTarget next = scrollableResultsIterator.next();
|
|
||||||
if (matches.add(next)) {
|
|
||||||
|
|
||||||
TranslateConceptResult translationMatch = new TranslateConceptResult();
|
|
||||||
if (next.getEquivalence() != null) {
|
|
||||||
translationMatch.setEquivalence(
|
|
||||||
next.getEquivalence().toCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
translationMatch.setCode(next.getCode());
|
|
||||||
translationMatch.setSystem(next.getSystem());
|
|
||||||
translationMatch.setSystemVersion(next.getSystemVersion());
|
|
||||||
translationMatch.setDisplay(next.getDisplay());
|
|
||||||
translationMatch.setValueSet(next.getValueSet());
|
|
||||||
translationMatch.setSystemVersion(next.getSystemVersion());
|
|
||||||
translationMatch.setConceptMapUrl(next.getConceptMapUrl());
|
|
||||||
|
|
||||||
targets.add(translationMatch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ourLastResultsFromTranslationCache = false; // For testing.
|
|
||||||
myMemoryCacheService.put(MemoryCacheService.CacheEnum.CONCEPT_TRANSLATION, translationQuery, targets);
|
|
||||||
retVal.getResults().addAll(targets);
|
|
||||||
} else {
|
|
||||||
ourLastResultsFromTranslationCache = true; // For testing.
|
|
||||||
retVal.getResults().addAll(cachedTargets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTranslationResult(retVal);
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(propagation = Propagation.REQUIRED)
|
|
||||||
public TranslateConceptResults translateWithReverse(TranslationRequest theTranslationRequest) {
|
|
||||||
TranslateConceptResults retVal = new TranslateConceptResults();
|
|
||||||
|
|
||||||
CriteriaBuilder criteriaBuilder = myEntityManager.getCriteriaBuilder();
|
|
||||||
CriteriaQuery<TermConceptMapGroupElement> query = criteriaBuilder.createQuery(TermConceptMapGroupElement.class);
|
|
||||||
Root<TermConceptMapGroupElement> root = query.from(TermConceptMapGroupElement.class);
|
|
||||||
|
|
||||||
Join<TermConceptMapGroupElement, TermConceptMapGroupElementTarget> targetJoin =
|
|
||||||
root.join("myConceptMapGroupElementTargets");
|
|
||||||
Join<TermConceptMapGroupElement, TermConceptMapGroup> groupJoin = root.join("myConceptMapGroup");
|
|
||||||
Join<TermConceptMapGroup, TermConceptMap> conceptMapJoin = groupJoin.join("myConceptMap");
|
|
||||||
|
|
||||||
List<TranslationQuery> translationQueries = theTranslationRequest.getTranslationQueries();
|
|
||||||
List<TranslateConceptResult> cachedElements;
|
|
||||||
ArrayList<Predicate> predicates;
|
|
||||||
Coding coding;
|
|
||||||
|
|
||||||
// -- get the latest ConceptMapVersion if theTranslationRequest has ConceptMap url but no ConceptMap version
|
|
||||||
String latestConceptMapVersion = null;
|
|
||||||
if (theTranslationRequest.hasUrl() && !theTranslationRequest.hasConceptMapVersion())
|
|
||||||
latestConceptMapVersion = getLatestConceptMapVersion(theTranslationRequest);
|
|
||||||
|
|
||||||
for (TranslationQuery translationQuery : translationQueries) {
|
|
||||||
cachedElements = myMemoryCacheService.getIfPresent(
|
|
||||||
MemoryCacheService.CacheEnum.CONCEPT_TRANSLATION_REVERSE, translationQuery);
|
|
||||||
if (cachedElements == null) {
|
|
||||||
final List<TranslateConceptResult> elements = new ArrayList<>();
|
|
||||||
|
|
||||||
predicates = new ArrayList<>();
|
|
||||||
|
|
||||||
coding = translationQuery.getCoding();
|
|
||||||
String targetCode;
|
|
||||||
String targetCodeSystem = null;
|
|
||||||
if (coding.hasCode()) {
|
|
||||||
predicates.add(criteriaBuilder.equal(targetJoin.get("myCode"), coding.getCode()));
|
|
||||||
targetCode = coding.getCode();
|
|
||||||
} else {
|
|
||||||
throw new InvalidRequestException(
|
|
||||||
Msg.code(843) + "A code must be provided for translation to occur.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (coding.hasSystem()) {
|
|
||||||
predicates.add(criteriaBuilder.equal(groupJoin.get("myTarget"), coding.getSystem()));
|
|
||||||
targetCodeSystem = coding.getSystem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (coding.hasVersion()) {
|
|
||||||
predicates.add(criteriaBuilder.equal(groupJoin.get("myTargetVersion"), coding.getVersion()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translationQuery.hasUrl()) {
|
|
||||||
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myUrl"), translationQuery.getUrl()));
|
|
||||||
if (translationQuery.hasConceptMapVersion()) {
|
|
||||||
// both url and conceptMapVersion
|
|
||||||
predicates.add(criteriaBuilder.equal(
|
|
||||||
conceptMapJoin.get("myVersion"), translationQuery.getConceptMapVersion()));
|
|
||||||
} else {
|
|
||||||
if (StringUtils.isNotBlank(latestConceptMapVersion)) {
|
|
||||||
// only url and use latestConceptMapVersion
|
|
||||||
predicates.add(
|
|
||||||
criteriaBuilder.equal(conceptMapJoin.get("myVersion"), latestConceptMapVersion));
|
|
||||||
} else {
|
|
||||||
predicates.add(criteriaBuilder.isNull(conceptMapJoin.get("myVersion")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translationQuery.hasTargetSystem()) {
|
|
||||||
predicates.add(
|
|
||||||
criteriaBuilder.equal(groupJoin.get("mySource"), translationQuery.getTargetSystem()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translationQuery.hasSource()) {
|
|
||||||
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myTarget"), translationQuery.getSource()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translationQuery.hasTarget()) {
|
|
||||||
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("mySource"), translationQuery.getTarget()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translationQuery.hasResourceId()) {
|
|
||||||
IIdType resourceId = translationQuery.getResourceId();
|
|
||||||
JpaPid resourcePid =
|
|
||||||
myIdHelperService.getPidOrThrowException(RequestPartitionId.defaultPartition(), resourceId);
|
|
||||||
predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myResourcePid"), resourcePid.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Predicate outerPredicate = criteriaBuilder.and(predicates.toArray(new Predicate[0]));
|
|
||||||
query.where(outerPredicate);
|
|
||||||
|
|
||||||
// Use scrollable results.
|
|
||||||
final TypedQuery<TermConceptMapGroupElement> typedQuery =
|
|
||||||
myEntityManager.createQuery(query.select(root));
|
|
||||||
org.hibernate.query.Query<TermConceptMapGroupElement> hibernateQuery =
|
|
||||||
(org.hibernate.query.Query<TermConceptMapGroupElement>) typedQuery;
|
|
||||||
hibernateQuery.setFetchSize(myFetchSize);
|
|
||||||
ScrollableResults scrollableResults = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY);
|
|
||||||
try (ScrollableResultsIterator<TermConceptMapGroupElement> scrollableResultsIterator =
|
|
||||||
new ScrollableResultsIterator<>(scrollableResults)) {
|
|
||||||
|
|
||||||
Set<TermConceptMapGroupElementTarget> matches = new HashSet<>();
|
|
||||||
while (scrollableResultsIterator.hasNext()) {
|
|
||||||
TermConceptMapGroupElement nextElement = scrollableResultsIterator.next();
|
|
||||||
|
|
||||||
/* TODO: The invocation of the size() below does not seem to be necessary but for some reason,
|
|
||||||
* but removing it causes tests in TerminologySvcImplR4Test to fail. We use the outcome
|
|
||||||
* in a trace log to avoid ErrorProne flagging an unused return value.
|
|
||||||
*/
|
|
||||||
int size =
|
|
||||||
nextElement.getConceptMapGroupElementTargets().size();
|
|
||||||
ourLog.trace("Have {} targets", size);
|
|
||||||
|
|
||||||
myEntityManager.detach(nextElement);
|
|
||||||
|
|
||||||
if (isNotBlank(targetCode)) {
|
|
||||||
for (TermConceptMapGroupElementTarget next :
|
|
||||||
nextElement.getConceptMapGroupElementTargets()) {
|
|
||||||
if (matches.add(next)) {
|
|
||||||
if (isBlank(targetCodeSystem)
|
|
||||||
|| StringUtils.equals(targetCodeSystem, next.getSystem())) {
|
|
||||||
if (StringUtils.equals(targetCode, next.getCode())) {
|
|
||||||
TranslateConceptResult translationMatch = new TranslateConceptResult();
|
|
||||||
translationMatch.setCode(nextElement.getCode());
|
|
||||||
translationMatch.setSystem(nextElement.getSystem());
|
|
||||||
translationMatch.setSystemVersion(nextElement.getSystemVersion());
|
|
||||||
translationMatch.setDisplay(nextElement.getDisplay());
|
|
||||||
translationMatch.setValueSet(nextElement.getValueSet());
|
|
||||||
translationMatch.setSystemVersion(nextElement.getSystemVersion());
|
|
||||||
translationMatch.setConceptMapUrl(nextElement.getConceptMapUrl());
|
|
||||||
if (next.getEquivalence() != null) {
|
|
||||||
translationMatch.setEquivalence(
|
|
||||||
next.getEquivalence().toCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alreadyContainsMapping(elements, translationMatch)
|
|
||||||
|| alreadyContainsMapping(retVal.getResults(), translationMatch)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
elements.add(translationMatch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ourLastResultsFromTranslationWithReverseCache = false; // For testing.
|
|
||||||
myMemoryCacheService.put(
|
|
||||||
MemoryCacheService.CacheEnum.CONCEPT_TRANSLATION_REVERSE, translationQuery, elements);
|
|
||||||
retVal.getResults().addAll(elements);
|
|
||||||
} else {
|
|
||||||
ourLastResultsFromTranslationWithReverseCache = true; // For testing.
|
|
||||||
retVal.getResults().addAll(cachedElements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTranslationResult(retVal);
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean alreadyContainsMapping(
|
|
||||||
List<TranslateConceptResult> elements, TranslateConceptResult translationMatch) {
|
|
||||||
for (TranslateConceptResult nextExistingElement : elements) {
|
|
||||||
if (StringUtils.equals(nextExistingElement.getSystem(), translationMatch.getSystem())) {
|
|
||||||
if (StringUtils.equals(nextExistingElement.getSystemVersion(), translationMatch.getSystemVersion())) {
|
|
||||||
if (StringUtils.equals(nextExistingElement.getCode(), translationMatch.getCode())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteConceptMap(ResourceTable theResourceTable) {
|
public void deleteConceptMap(ResourceTable theResourceTable) {
|
||||||
// Get existing entity so it can be deleted.
|
// Get existing entity so it can be deleted.
|
||||||
Optional<TermConceptMap> optionalExistingTermConceptMapById =
|
Optional<TermConceptMap> optionalExistingTermConceptMapById =
|
||||||
|
@ -671,34 +305,6 @@ public class TermConceptMappingSvcImpl implements ITermConceptMappingSvc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case for the translate operation with url and without
|
|
||||||
// conceptMapVersion, find the latest conecptMapVersion
|
|
||||||
private String getLatestConceptMapVersion(TranslationRequest theTranslationRequest) {
|
|
||||||
|
|
||||||
Pageable page = PageRequest.of(0, 1);
|
|
||||||
List<TermConceptMap> theConceptMapList = myConceptMapDao.getTermConceptMapEntitiesByUrlOrderByMostRecentUpdate(
|
|
||||||
page, theTranslationRequest.getUrl());
|
|
||||||
if (!theConceptMapList.isEmpty()) {
|
|
||||||
return theConceptMapList.get(0).getVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildTranslationResult(TranslateConceptResults theTranslationResult) {
|
|
||||||
|
|
||||||
String msg;
|
|
||||||
if (theTranslationResult.getResults().isEmpty()) {
|
|
||||||
theTranslationResult.setResult(false);
|
|
||||||
msg = myContext.getLocalizer().getMessage(TermConceptMappingSvcImpl.class, "noMatchesFound");
|
|
||||||
theTranslationResult.setMessage(msg);
|
|
||||||
} else {
|
|
||||||
theTranslationResult.setResult(true);
|
|
||||||
msg = myContext.getLocalizer().getMessage(TermConceptMappingSvcImpl.class, "matchesFound");
|
|
||||||
theTranslationResult.setMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is present only for unit tests, do not call from client code
|
* This method is present only for unit tests, do not call from client code
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* 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%
|
||||||
|
*/
|
||||||
|
package ca.uhn.fhir.jpa.term.api;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||||
|
import ca.uhn.fhir.context.support.TranslateConceptResults;
|
||||||
|
import ca.uhn.fhir.jpa.api.model.TranslationRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the terminology translate functions
|
||||||
|
*/
|
||||||
|
public interface ITermConceptClientMappingSvc extends IValidationSupport {
|
||||||
|
|
||||||
|
TranslateConceptResults translate(TranslationRequest theTranslationRequest);
|
||||||
|
|
||||||
|
TranslateConceptResults translateWithReverse(TranslationRequest theTranslationRequest);
|
||||||
|
}
|
|
@ -19,17 +19,13 @@
|
||||||
*/
|
*/
|
||||||
package ca.uhn.fhir.jpa.term.api;
|
package ca.uhn.fhir.jpa.term.api;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
|
||||||
import ca.uhn.fhir.context.support.TranslateConceptResults;
|
|
||||||
import ca.uhn.fhir.jpa.api.model.TranslationRequest;
|
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import org.hl7.fhir.r4.model.ConceptMap;
|
import org.hl7.fhir.r4.model.ConceptMap;
|
||||||
|
|
||||||
public interface ITermConceptMappingSvc extends IValidationSupport {
|
/**
|
||||||
|
* Represents ConceptMap translation and persistence operations
|
||||||
TranslateConceptResults translate(TranslationRequest theTranslationRequest);
|
*/
|
||||||
|
public interface ITermConceptMappingSvc extends ITermConceptClientMappingSvc {
|
||||||
TranslateConceptResults translateWithReverse(TranslationRequest theTranslationRequest);
|
|
||||||
|
|
||||||
void deleteConceptMapAndChildren(ResourceTable theResourceTable);
|
void deleteConceptMapAndChildren(ResourceTable theResourceTable);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue