
001package ca.uhn.fhir.jpa.term; 002 003/*- 004 * #%L 005 * HAPI FHIR JPA Server 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.context.FhirContext; 025import ca.uhn.fhir.context.support.TranslateConceptResult; 026import ca.uhn.fhir.context.support.TranslateConceptResults; 027import ca.uhn.fhir.jpa.api.model.TranslationQuery; 028import ca.uhn.fhir.jpa.api.model.TranslationRequest; 029import ca.uhn.fhir.jpa.dao.data.ITermConceptMapDao; 030import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupDao; 031import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementDao; 032import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementTargetDao; 033import ca.uhn.fhir.jpa.entity.TermConceptMap; 034import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; 035import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; 036import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; 037import ca.uhn.fhir.jpa.model.entity.ResourceTable; 038import ca.uhn.fhir.jpa.term.api.ITermConceptMappingSvc; 039import ca.uhn.fhir.jpa.util.MemoryCacheService; 040import ca.uhn.fhir.jpa.util.ScrollableResultsIterator; 041import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 042import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 043import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; 044import ca.uhn.fhir.util.ValidateUtil; 045import com.google.common.annotations.VisibleForTesting; 046import org.apache.commons.lang3.StringUtils; 047import org.hibernate.ScrollMode; 048import org.hibernate.ScrollableResults; 049import org.hl7.fhir.exceptions.FHIRException; 050import org.hl7.fhir.instance.model.api.IBase; 051import org.hl7.fhir.instance.model.api.IBaseCoding; 052import org.hl7.fhir.r4.model.BooleanType; 053import org.hl7.fhir.r4.model.CodeType; 054import org.hl7.fhir.r4.model.CodeableConcept; 055import org.hl7.fhir.r4.model.Coding; 056import org.hl7.fhir.r4.model.ConceptMap; 057import org.hl7.fhir.r4.model.IdType; 058import org.hl7.fhir.r4.model.Parameters; 059import org.hl7.fhir.r4.model.StringType; 060import org.hl7.fhir.r4.model.UriType; 061import org.slf4j.Logger; 062import org.slf4j.LoggerFactory; 063import org.springframework.beans.factory.annotation.Autowired; 064import org.springframework.data.domain.PageRequest; 065import org.springframework.data.domain.Pageable; 066import org.springframework.transaction.annotation.Propagation; 067import org.springframework.transaction.annotation.Transactional; 068 069import javax.persistence.EntityManager; 070import javax.persistence.PersistenceContext; 071import javax.persistence.PersistenceContextType; 072import javax.persistence.TypedQuery; 073import javax.persistence.criteria.CriteriaBuilder; 074import javax.persistence.criteria.CriteriaQuery; 075import javax.persistence.criteria.Join; 076import javax.persistence.criteria.Predicate; 077import javax.persistence.criteria.Root; 078import java.util.ArrayList; 079import java.util.HashSet; 080import java.util.List; 081import java.util.Optional; 082import java.util.Set; 083 084import static ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.isPlaceholder; 085import static org.apache.commons.lang3.StringUtils.isBlank; 086import static org.apache.commons.lang3.StringUtils.isNotBlank; 087 088public class TermConceptMappingSvcImpl implements ITermConceptMappingSvc { 089 090 private static final Logger ourLog = LoggerFactory.getLogger(TermConceptMappingSvcImpl.class); 091 private static boolean ourLastResultsFromTranslationCache; // For testing. 092 private static boolean ourLastResultsFromTranslationWithReverseCache; // For testing. 093 private final int myFetchSize = BaseTermReadSvcImpl.DEFAULT_FETCH_SIZE; 094 @Autowired 095 protected ITermConceptMapDao myConceptMapDao; 096 @Autowired 097 protected ITermConceptMapGroupDao myConceptMapGroupDao; 098 @Autowired 099 protected ITermConceptMapGroupElementDao myConceptMapGroupElementDao; 100 @Autowired 101 protected ITermConceptMapGroupElementTargetDao myConceptMapGroupElementTargetDao; 102 @PersistenceContext(type = PersistenceContextType.TRANSACTION) 103 protected EntityManager myEntityManager; 104 @Autowired 105 private FhirContext myContext; 106 @Autowired 107 private MemoryCacheService myMemoryCacheService; 108 109 @Override 110 @Transactional 111 public void deleteConceptMapAndChildren(ResourceTable theResourceTable) { 112 deleteConceptMap(theResourceTable); 113 } 114 115 @Override 116 public FhirContext getFhirContext() { 117 return myContext; 118 } 119 120 @Override 121 @Transactional 122 public TranslateConceptResults translateConcept(TranslateCodeRequest theRequest) { 123 TranslationRequest request = TranslationRequest.fromTranslateCodeRequest(theRequest); 124 if (request.hasReverse() && request.getReverseAsBoolean()) { 125 return translateWithReverse(request); 126 } 127 128 return translate(request); 129 } 130 131 @Override 132 @Transactional 133 public void storeTermConceptMapAndChildren(ResourceTable theResourceTable, ConceptMap theConceptMap) { 134 135 ValidateUtil.isTrueOrThrowInvalidRequest(theResourceTable != null, "No resource supplied"); 136 if (isPlaceholder(theConceptMap)) { 137 ourLog.info("Not storing TermConceptMap for placeholder {}", theConceptMap.getIdElement().toVersionless().getValueAsString()); 138 return; 139 } 140 141 ValidateUtil.isNotBlankOrThrowUnprocessableEntity(theConceptMap.getUrl(), "ConceptMap has no value for ConceptMap.url"); 142 ourLog.info("Storing TermConceptMap for {}", theConceptMap.getIdElement().toVersionless().getValueAsString()); 143 144 TermConceptMap termConceptMap = new TermConceptMap(); 145 termConceptMap.setResource(theResourceTable); 146 termConceptMap.setUrl(theConceptMap.getUrl()); 147 termConceptMap.setVersion(theConceptMap.getVersion()); 148 149 String source = theConceptMap.hasSourceUriType() ? theConceptMap.getSourceUriType().getValueAsString() : null; 150 String target = theConceptMap.hasTargetUriType() ? theConceptMap.getTargetUriType().getValueAsString() : null; 151 152 /* 153 * If this is a mapping between "resources" instead of purely between 154 * "concepts" (this is a weird concept that is technically possible, at least as of 155 * FHIR R4), don't try to store the mappings. 156 * 157 * See here for a description of what that is: 158 * http://hl7.org/fhir/conceptmap.html#bnr 159 */ 160 if ("StructureDefinition".equals(new IdType(source).getResourceType()) || 161 "StructureDefinition".equals(new IdType(target).getResourceType())) { 162 return; 163 } 164 165 if (source == null && theConceptMap.hasSourceCanonicalType()) { 166 source = theConceptMap.getSourceCanonicalType().getValueAsString(); 167 } 168 if (target == null && theConceptMap.hasTargetCanonicalType()) { 169 target = theConceptMap.getTargetCanonicalType().getValueAsString(); 170 } 171 172 /* 173 * For now we always delete old versions. At some point, it would be nice to allow configuration to keep old versions. 174 */ 175 deleteConceptMap(theResourceTable); 176 177 /* 178 * Do the upload. 179 */ 180 String conceptMapUrl = termConceptMap.getUrl(); 181 String conceptMapVersion = termConceptMap.getVersion(); 182 Optional<TermConceptMap> optionalExistingTermConceptMapByUrl; 183 if (isBlank(conceptMapVersion)) { 184 optionalExistingTermConceptMapByUrl = myConceptMapDao.findTermConceptMapByUrlAndNullVersion(conceptMapUrl); 185 } else { 186 optionalExistingTermConceptMapByUrl = myConceptMapDao.findTermConceptMapByUrlAndVersion(conceptMapUrl, conceptMapVersion); 187 } 188 if (!optionalExistingTermConceptMapByUrl.isPresent()) { 189 try { 190 if (isNotBlank(source)) { 191 termConceptMap.setSource(source); 192 } 193 if (isNotBlank(target)) { 194 termConceptMap.setTarget(target); 195 } 196 } catch (FHIRException fe) { 197 throw new InternalErrorException(Msg.code(837) + fe); 198 } 199 termConceptMap = myConceptMapDao.save(termConceptMap); 200 int codesSaved = 0; 201 202 TermConceptMapGroup termConceptMapGroup; 203 for (ConceptMap.ConceptMapGroupComponent group : theConceptMap.getGroup()) { 204 205 String groupSource = group.getSource(); 206 if (isBlank(groupSource)) { 207 groupSource = source; 208 } 209 if (isBlank(groupSource)) { 210 throw new UnprocessableEntityException(Msg.code(838) + "ConceptMap[url='" + theConceptMap.getUrl() + "'] contains at least one group without a value in ConceptMap.group.source"); 211 } 212 213 String groupTarget = group.getTarget(); 214 if (isBlank(groupTarget)) { 215 groupTarget = target; 216 } 217 if (isBlank(groupTarget)) { 218 throw new UnprocessableEntityException(Msg.code(839) + "ConceptMap[url='" + theConceptMap.getUrl() + "'] contains at least one group without a value in ConceptMap.group.target"); 219 } 220 221 termConceptMapGroup = new TermConceptMapGroup(); 222 termConceptMapGroup.setConceptMap(termConceptMap); 223 termConceptMapGroup.setSource(groupSource); 224 termConceptMapGroup.setSourceVersion(group.getSourceVersion()); 225 termConceptMapGroup.setTarget(groupTarget); 226 termConceptMapGroup.setTargetVersion(group.getTargetVersion()); 227 termConceptMapGroup = myConceptMapGroupDao.save(termConceptMapGroup); 228 229 if (group.hasElement()) { 230 TermConceptMapGroupElement termConceptMapGroupElement; 231 for (ConceptMap.SourceElementComponent element : group.getElement()) { 232 if (isBlank(element.getCode())) { 233 continue; 234 } 235 termConceptMapGroupElement = new TermConceptMapGroupElement(); 236 termConceptMapGroupElement.setConceptMapGroup(termConceptMapGroup); 237 termConceptMapGroupElement.setCode(element.getCode()); 238 termConceptMapGroupElement.setDisplay(element.getDisplay()); 239 termConceptMapGroupElement = myConceptMapGroupElementDao.save(termConceptMapGroupElement); 240 241 if (element.hasTarget()) { 242 TermConceptMapGroupElementTarget termConceptMapGroupElementTarget; 243 for (ConceptMap.TargetElementComponent elementTarget : element.getTarget()) { 244 if (isBlank(elementTarget.getCode())) { 245 continue; 246 } 247 termConceptMapGroupElementTarget = new TermConceptMapGroupElementTarget(); 248 termConceptMapGroupElementTarget.setConceptMapGroupElement(termConceptMapGroupElement); 249 termConceptMapGroupElementTarget.setCode(elementTarget.getCode()); 250 termConceptMapGroupElementTarget.setDisplay(elementTarget.getDisplay()); 251 termConceptMapGroupElementTarget.setEquivalence(elementTarget.getEquivalence()); 252 myConceptMapGroupElementTargetDao.save(termConceptMapGroupElementTarget); 253 254 if (++codesSaved % 250 == 0) { 255 ourLog.info("Have saved {} codes in ConceptMap", codesSaved); 256 myConceptMapGroupElementTargetDao.flush(); 257 } 258 } 259 } 260 } 261 } 262 } 263 264 } else { 265 TermConceptMap existingTermConceptMap = optionalExistingTermConceptMapByUrl.get(); 266 267 if (isBlank(conceptMapVersion)) { 268 String msg = myContext.getLocalizer().getMessage( 269 BaseTermReadSvcImpl.class, 270 "cannotCreateDuplicateConceptMapUrl", 271 conceptMapUrl, 272 existingTermConceptMap.getResource().getIdDt().toUnqualifiedVersionless().getValue()); 273 throw new UnprocessableEntityException(Msg.code(840) + msg); 274 275 } else { 276 String msg = myContext.getLocalizer().getMessage( 277 BaseTermReadSvcImpl.class, 278 "cannotCreateDuplicateConceptMapUrlAndVersion", 279 conceptMapUrl, conceptMapVersion, 280 existingTermConceptMap.getResource().getIdDt().toUnqualifiedVersionless().getValue()); 281 throw new UnprocessableEntityException(Msg.code(841) + msg); 282 } 283 } 284 285 ourLog.info("Done storing TermConceptMap[{}] for {}", termConceptMap.getId(), theConceptMap.getIdElement().toVersionless().getValueAsString()); 286 } 287 288 @Override 289 @Transactional(propagation = Propagation.REQUIRED) 290 public TranslateConceptResults translate(TranslationRequest theTranslationRequest) { 291 TranslateConceptResults retVal = new TranslateConceptResults(); 292 293 CriteriaBuilder criteriaBuilder = myEntityManager.getCriteriaBuilder(); 294 CriteriaQuery<TermConceptMapGroupElementTarget> query = criteriaBuilder.createQuery(TermConceptMapGroupElementTarget.class); 295 Root<TermConceptMapGroupElementTarget> root = query.from(TermConceptMapGroupElementTarget.class); 296 297 Join<TermConceptMapGroupElementTarget, TermConceptMapGroupElement> elementJoin = root.join("myConceptMapGroupElement"); 298 Join<TermConceptMapGroupElement, TermConceptMapGroup> groupJoin = elementJoin.join("myConceptMapGroup"); 299 Join<TermConceptMapGroup, TermConceptMap> conceptMapJoin = groupJoin.join("myConceptMap"); 300 301 List<TranslationQuery> translationQueries = theTranslationRequest.getTranslationQueries(); 302 List<TranslateConceptResult> cachedTargets; 303 ArrayList<Predicate> predicates; 304 Coding coding; 305 306 //-- get the latest ConceptMapVersion if theTranslationRequest has ConceptMap url but no ConceptMap version 307 String latestConceptMapVersion = null; 308 if (theTranslationRequest.hasUrl() && !theTranslationRequest.hasConceptMapVersion()) 309 latestConceptMapVersion = getLatestConceptMapVersion(theTranslationRequest); 310 311 for (TranslationQuery translationQuery : translationQueries) { 312 cachedTargets = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.CONCEPT_TRANSLATION, translationQuery); 313 if (cachedTargets == null) { 314 final List<TranslateConceptResult> targets = new ArrayList<>(); 315 316 predicates = new ArrayList<>(); 317 318 coding = translationQuery.getCoding(); 319 if (coding.hasCode()) { 320 predicates.add(criteriaBuilder.equal(elementJoin.get("myCode"), coding.getCode())); 321 } else { 322 throw new InvalidRequestException(Msg.code(842) + "A code must be provided for translation to occur."); 323 } 324 325 if (coding.hasSystem()) { 326 predicates.add(criteriaBuilder.equal(groupJoin.get("mySource"), coding.getSystem())); 327 } 328 329 if (coding.hasVersion()) { 330 predicates.add(criteriaBuilder.equal(groupJoin.get("mySourceVersion"), coding.getVersion())); 331 } 332 333 if (translationQuery.hasTargetSystem()) { 334 predicates.add(criteriaBuilder.equal(groupJoin.get("myTarget"), translationQuery.getTargetSystem().getValueAsString())); 335 } 336 337 if (translationQuery.hasUrl()) { 338 predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myUrl"), translationQuery.getUrl().getValueAsString())); 339 if (translationQuery.hasConceptMapVersion()) { 340 // both url and conceptMapVersion 341 predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myVersion"), translationQuery.getConceptMapVersion().getValueAsString())); 342 } else { 343 if (StringUtils.isNotBlank(latestConceptMapVersion)) { 344 // only url and use latestConceptMapVersion 345 predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myVersion"), latestConceptMapVersion)); 346 } else { 347 predicates.add(criteriaBuilder.isNull(conceptMapJoin.get("myVersion"))); 348 } 349 } 350 } 351 352 if (translationQuery.hasSource()) { 353 predicates.add(criteriaBuilder.equal(conceptMapJoin.get("mySource"), translationQuery.getSource().getValueAsString())); 354 } 355 356 if (translationQuery.hasTarget()) { 357 predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myTarget"), translationQuery.getTarget().getValueAsString())); 358 } 359 360 if (translationQuery.hasResourceId()) { 361 predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myResourcePid"), translationQuery.getResourceId())); 362 } 363 364 Predicate outerPredicate = criteriaBuilder.and(predicates.toArray(new Predicate[0])); 365 query.where(outerPredicate); 366 367 // Use scrollable results. 368 final TypedQuery<TermConceptMapGroupElementTarget> typedQuery = myEntityManager.createQuery(query.select(root)); 369 org.hibernate.query.Query<TermConceptMapGroupElementTarget> hibernateQuery = (org.hibernate.query.Query<TermConceptMapGroupElementTarget>) typedQuery; 370 hibernateQuery.setFetchSize(myFetchSize); 371 ScrollableResults scrollableResults = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY); 372 try (ScrollableResultsIterator<TermConceptMapGroupElementTarget> scrollableResultsIterator = new ScrollableResultsIterator<>(scrollableResults)) { 373 374 Set<TermConceptMapGroupElementTarget> matches = new HashSet<>(); 375 while (scrollableResultsIterator.hasNext()) { 376 TermConceptMapGroupElementTarget next = scrollableResultsIterator.next(); 377 if (matches.add(next)) { 378 379 TranslateConceptResult translationMatch = new TranslateConceptResult(); 380 if (next.getEquivalence() != null) { 381 translationMatch.setEquivalence(next.getEquivalence().toCode()); 382 } 383 384 translationMatch.setCode(next.getCode()); 385 translationMatch.setSystem(next.getSystem()); 386 translationMatch.setSystemVersion(next.getSystemVersion()); 387 translationMatch.setDisplay(next.getDisplay()); 388 translationMatch.setValueSet(next.getValueSet()); 389 translationMatch.setSystemVersion(next.getSystemVersion()); 390 translationMatch.setConceptMapUrl(next.getConceptMapUrl()); 391 392 targets.add(translationMatch); 393 } 394 } 395 396 } 397 398 ourLastResultsFromTranslationCache = false; // For testing. 399 myMemoryCacheService.put(MemoryCacheService.CacheEnum.CONCEPT_TRANSLATION, translationQuery, targets); 400 retVal.getResults().addAll(targets); 401 } else { 402 ourLastResultsFromTranslationCache = true; // For testing. 403 retVal.getResults().addAll(cachedTargets); 404 } 405 } 406 407 buildTranslationResult(retVal); 408 return retVal; 409 } 410 411 @Override 412 @Transactional(propagation = Propagation.REQUIRED) 413 public TranslateConceptResults translateWithReverse(TranslationRequest theTranslationRequest) { 414 TranslateConceptResults retVal = new TranslateConceptResults(); 415 416 CriteriaBuilder criteriaBuilder = myEntityManager.getCriteriaBuilder(); 417 CriteriaQuery<TermConceptMapGroupElement> query = criteriaBuilder.createQuery(TermConceptMapGroupElement.class); 418 Root<TermConceptMapGroupElement> root = query.from(TermConceptMapGroupElement.class); 419 420 Join<TermConceptMapGroupElement, TermConceptMapGroupElementTarget> targetJoin = root.join("myConceptMapGroupElementTargets"); 421 Join<TermConceptMapGroupElement, TermConceptMapGroup> groupJoin = root.join("myConceptMapGroup"); 422 Join<TermConceptMapGroup, TermConceptMap> conceptMapJoin = groupJoin.join("myConceptMap"); 423 424 List<TranslationQuery> translationQueries = theTranslationRequest.getTranslationQueries(); 425 List<TranslateConceptResult> cachedElements; 426 ArrayList<Predicate> predicates; 427 Coding coding; 428 429 //-- get the latest ConceptMapVersion if theTranslationRequest has ConceptMap url but no ConceptMap version 430 String latestConceptMapVersion = null; 431 if (theTranslationRequest.hasUrl() && !theTranslationRequest.hasConceptMapVersion()) 432 latestConceptMapVersion = getLatestConceptMapVersion(theTranslationRequest); 433 434 for (TranslationQuery translationQuery : translationQueries) { 435 cachedElements = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.CONCEPT_TRANSLATION_REVERSE, translationQuery); 436 if (cachedElements == null) { 437 final List<TranslateConceptResult> elements = new ArrayList<>(); 438 439 predicates = new ArrayList<>(); 440 441 coding = translationQuery.getCoding(); 442 String targetCode; 443 String targetCodeSystem = null; 444 if (coding.hasCode()) { 445 predicates.add(criteriaBuilder.equal(targetJoin.get("myCode"), coding.getCode())); 446 targetCode = coding.getCode(); 447 } else { 448 throw new InvalidRequestException(Msg.code(843) + "A code must be provided for translation to occur."); 449 } 450 451 if (coding.hasSystem()) { 452 predicates.add(criteriaBuilder.equal(groupJoin.get("myTarget"), coding.getSystem())); 453 targetCodeSystem = coding.getSystem(); 454 } 455 456 if (coding.hasVersion()) { 457 predicates.add(criteriaBuilder.equal(groupJoin.get("myTargetVersion"), coding.getVersion())); 458 } 459 460 if (translationQuery.hasUrl()) { 461 predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myUrl"), translationQuery.getUrl().getValueAsString())); 462 if (translationQuery.hasConceptMapVersion()) { 463 // both url and conceptMapVersion 464 predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myVersion"), translationQuery.getConceptMapVersion().getValueAsString())); 465 } else { 466 if (StringUtils.isNotBlank(latestConceptMapVersion)) { 467 // only url and use latestConceptMapVersion 468 predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myVersion"), latestConceptMapVersion)); 469 } else { 470 predicates.add(criteriaBuilder.isNull(conceptMapJoin.get("myVersion"))); 471 } 472 } 473 } 474 475 if (translationQuery.hasTargetSystem()) { 476 predicates.add(criteriaBuilder.equal(groupJoin.get("mySource"), translationQuery.getTargetSystem().getValueAsString())); 477 } 478 479 if (translationQuery.hasSource()) { 480 predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myTarget"), translationQuery.getSource().getValueAsString())); 481 } 482 483 if (translationQuery.hasTarget()) { 484 predicates.add(criteriaBuilder.equal(conceptMapJoin.get("mySource"), translationQuery.getTarget().getValueAsString())); 485 } 486 487 if (translationQuery.hasResourceId()) { 488 predicates.add(criteriaBuilder.equal(conceptMapJoin.get("myResourcePid"), translationQuery.getResourceId())); 489 } 490 491 Predicate outerPredicate = criteriaBuilder.and(predicates.toArray(new Predicate[0])); 492 query.where(outerPredicate); 493 494 // Use scrollable results. 495 final TypedQuery<TermConceptMapGroupElement> typedQuery = myEntityManager.createQuery(query.select(root)); 496 org.hibernate.query.Query<TermConceptMapGroupElement> hibernateQuery = (org.hibernate.query.Query<TermConceptMapGroupElement>) typedQuery; 497 hibernateQuery.setFetchSize(myFetchSize); 498 ScrollableResults scrollableResults = hibernateQuery.scroll(ScrollMode.FORWARD_ONLY); 499 try (ScrollableResultsIterator<TermConceptMapGroupElement> scrollableResultsIterator = new ScrollableResultsIterator<>(scrollableResults)) { 500 501 Set<TermConceptMapGroupElementTarget> matches = new HashSet<>(); 502 while (scrollableResultsIterator.hasNext()) { 503 TermConceptMapGroupElement nextElement = scrollableResultsIterator.next(); 504 505 /* TODO: The invocation of the size() below does not seem to be necessary but for some reason, 506 * but removing it causes tests in TerminologySvcImplR4Test to fail. We use the outcome 507 * in a trace log to avoid ErrorProne flagging an unused return value. 508 */ 509 int size = nextElement.getConceptMapGroupElementTargets().size(); 510 ourLog.trace("Have {} targets", size); 511 512 myEntityManager.detach(nextElement); 513 514 if (isNotBlank(targetCode)) { 515 for (TermConceptMapGroupElementTarget next : nextElement.getConceptMapGroupElementTargets()) { 516 if (matches.add(next)) { 517 if (isBlank(targetCodeSystem) || StringUtils.equals(targetCodeSystem, next.getSystem())) { 518 if (StringUtils.equals(targetCode, next.getCode())) { 519 TranslateConceptResult translationMatch = new TranslateConceptResult(); 520 translationMatch.setCode(nextElement.getCode()); 521 translationMatch.setSystem(nextElement.getSystem()); 522 translationMatch.setSystemVersion(nextElement.getSystemVersion()); 523 translationMatch.setDisplay(nextElement.getDisplay()); 524 translationMatch.setValueSet(nextElement.getValueSet()); 525 translationMatch.setSystemVersion(nextElement.getSystemVersion()); 526 translationMatch.setConceptMapUrl(nextElement.getConceptMapUrl()); 527 if (next.getEquivalence() != null) { 528 translationMatch.setEquivalence(next.getEquivalence().toCode()); 529 } 530 531 if (alreadyContainsMapping(elements, translationMatch) || alreadyContainsMapping(retVal.getResults(), translationMatch)) { 532 continue; 533 } 534 535 elements.add(translationMatch); 536 } 537 } 538 539 } 540 } 541 } 542 } 543 544 } 545 546 ourLastResultsFromTranslationWithReverseCache = false; // For testing. 547 myMemoryCacheService.put(MemoryCacheService.CacheEnum.CONCEPT_TRANSLATION_REVERSE, translationQuery, elements); 548 retVal.getResults().addAll(elements); 549 } else { 550 ourLastResultsFromTranslationWithReverseCache = true; // For testing. 551 retVal.getResults().addAll(cachedElements); 552 } 553 } 554 555 buildTranslationResult(retVal); 556 return retVal; 557 } 558 559 private boolean alreadyContainsMapping(List<TranslateConceptResult> elements, TranslateConceptResult translationMatch) { 560 for (TranslateConceptResult nextExistingElement : elements) { 561 if (StringUtils.equals(nextExistingElement.getSystem(), translationMatch.getSystem())) { 562 if (StringUtils.equals(nextExistingElement.getSystemVersion(), translationMatch.getSystemVersion())) { 563 if (StringUtils.equals(nextExistingElement.getCode(), translationMatch.getCode())) { 564 return true; 565 } 566 } 567 } 568 } 569 return false; 570 } 571 572 public void deleteConceptMap(ResourceTable theResourceTable) { 573 // Get existing entity so it can be deleted. 574 Optional<TermConceptMap> optionalExistingTermConceptMapById = myConceptMapDao.findTermConceptMapByResourcePid(theResourceTable.getId()); 575 576 if (optionalExistingTermConceptMapById.isPresent()) { 577 TermConceptMap existingTermConceptMap = optionalExistingTermConceptMapById.get(); 578 579 ourLog.info("Deleting existing TermConceptMap[{}] and its children...", existingTermConceptMap.getId()); 580 for (TermConceptMapGroup group : existingTermConceptMap.getConceptMapGroups()) { 581 582 for (TermConceptMapGroupElement element : group.getConceptMapGroupElements()) { 583 584 for (TermConceptMapGroupElementTarget target : element.getConceptMapGroupElementTargets()) { 585 586 myConceptMapGroupElementTargetDao.deleteTermConceptMapGroupElementTargetById(target.getId()); 587 } 588 589 myConceptMapGroupElementDao.deleteTermConceptMapGroupElementById(element.getId()); 590 } 591 592 myConceptMapGroupDao.deleteTermConceptMapGroupById(group.getId()); 593 } 594 595 myConceptMapDao.deleteTermConceptMapById(existingTermConceptMap.getId()); 596 ourLog.info("Done deleting existing TermConceptMap[{}] and its children.", existingTermConceptMap.getId()); 597 } 598 } 599 600 // Special case for the translate operation with url and without 601 // conceptMapVersion, find the latest conecptMapVersion 602 private String getLatestConceptMapVersion(TranslationRequest theTranslationRequest) { 603 604 Pageable page = PageRequest.of(0, 1); 605 List<TermConceptMap> theConceptMapList = myConceptMapDao.getTermConceptMapEntitiesByUrlOrderByMostRecentUpdate(page, 606 theTranslationRequest.getUrl().asStringValue()); 607 if (!theConceptMapList.isEmpty()) { 608 return theConceptMapList.get(0).getVersion(); 609 } 610 611 return null; 612 } 613 614 private void buildTranslationResult(TranslateConceptResults theTranslationResult) { 615 616 String msg; 617 if (theTranslationResult.getResults().isEmpty()) { 618 theTranslationResult.setResult(false); 619 msg = myContext.getLocalizer().getMessage(TermConceptMappingSvcImpl.class, "noMatchesFound"); 620 theTranslationResult.setMessage(msg); 621 } else { 622 theTranslationResult.setResult(true); 623 msg = myContext.getLocalizer().getMessage(TermConceptMappingSvcImpl.class, "matchesFound"); 624 theTranslationResult.setMessage(msg); 625 } 626 627 } 628 629 630 /** 631 * This method is present only for unit tests, do not call from client code 632 */ 633 @VisibleForTesting 634 public static void clearOurLastResultsFromTranslationCache() { 635 ourLastResultsFromTranslationCache = false; 636 } 637 638 /** 639 * This method is present only for unit tests, do not call from client code 640 */ 641 @VisibleForTesting 642 public static void clearOurLastResultsFromTranslationWithReverseCache() { 643 ourLastResultsFromTranslationWithReverseCache = false; 644 } 645 646 /** 647 * This method is present only for unit tests, do not call from client code 648 */ 649 @VisibleForTesting 650 static boolean isOurLastResultsFromTranslationCache() { 651 return ourLastResultsFromTranslationCache; 652 } 653 654 /** 655 * This method is present only for unit tests, do not call from client code 656 */ 657 @VisibleForTesting 658 static boolean isOurLastResultsFromTranslationWithReverseCache() { 659 return ourLastResultsFromTranslationWithReverseCache; 660 } 661 662 public static Parameters toParameters(TranslateConceptResults theTranslationResult) { 663 Parameters retVal = new Parameters(); 664 665 retVal.addParameter().setName("result").setValue(new BooleanType(theTranslationResult.getResult())); 666 667 if (theTranslationResult.getMessage() != null) { 668 retVal.addParameter().setName("message").setValue(new StringType(theTranslationResult.getMessage())); 669 } 670 671 for (TranslateConceptResult translationMatch : theTranslationResult.getResults()) { 672 Parameters.ParametersParameterComponent matchParam = retVal.addParameter().setName("match"); 673 populateTranslateMatchParts(translationMatch, matchParam); 674 } 675 676 return retVal; 677 } 678 679 private static void populateTranslateMatchParts(TranslateConceptResult theTranslationMatch, Parameters.ParametersParameterComponent theParam) { 680 if (theTranslationMatch.getEquivalence() != null) { 681 theParam.addPart().setName("equivalence").setValue(new CodeType(theTranslationMatch.getEquivalence())); 682 } 683 684 if (isNotBlank(theTranslationMatch.getSystem()) || isNotBlank(theTranslationMatch.getCode()) || isNotBlank(theTranslationMatch.getDisplay())) { 685 Coding value = new Coding(theTranslationMatch.getSystem(), theTranslationMatch.getCode(), theTranslationMatch.getDisplay()); 686 687 if (isNotBlank(theTranslationMatch.getSystemVersion())) { 688 value.setVersion(theTranslationMatch.getSystemVersion()); 689 } 690 691 theParam.addPart().setName("concept").setValue(value); 692 } 693 694 if (isNotBlank(theTranslationMatch.getConceptMapUrl())) { 695 theParam.addPart().setName("source").setValue(new UriType(theTranslationMatch.getConceptMapUrl())); 696 } 697 } 698}