
001package org.hl7.fhir.r5.terminologies.client; 002 003import java.io.File; 004import java.io.IOException; 005import java.net.MalformedURLException; 006import java.net.URISyntaxException; 007import java.net.URL; 008import java.util.ArrayList; 009import java.util.Collections; 010import java.util.HashMap; 011import java.util.List; 012import java.util.Map; 013import java.util.Set; 014 015import org.apache.commons.lang3.exception.ExceptionUtils; 016import org.hl7.fhir.exceptions.TerminologyServiceException; 017import org.hl7.fhir.r5.context.ILoggingService; 018import org.hl7.fhir.r5.model.Bundle; 019import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; 020import org.hl7.fhir.r5.model.CodeSystem; 021import org.hl7.fhir.r5.model.Parameters; 022import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; 023import org.hl7.fhir.r5.model.UriType; 024import org.hl7.fhir.r5.model.ValueSet; 025import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 026import org.hl7.fhir.r5.terminologies.ImplicitValueSets; 027import org.hl7.fhir.r5.terminologies.ValueSetUtilities; 028import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.TerminologyClientContextUseType; 029import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache; 030import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedCodeSystem; 031import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet; 032import org.hl7.fhir.r5.utils.UserDataNames; 033import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 034import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 035import org.hl7.fhir.utilities.ToolingClientLogger; 036import org.hl7.fhir.utilities.Utilities; 037import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; 038import org.hl7.fhir.utilities.http.ManagedWebAccess; 039import org.hl7.fhir.utilities.json.model.JsonObject; 040import org.hl7.fhir.utilities.json.parser.JsonParser; 041 042@MarkedToMoveToAdjunctPackage 043public class TerminologyClientManager { 044 045 private ImplicitValueSets implicitValueSets; 046 047 public class ServerOptionList { 048 private List<String> authoritative = new ArrayList<String>(); 049 private List<String> candidates = new ArrayList<String>(); 050 051 public ServerOptionList(String address) { 052 candidates.add(address); 053 } 054 055 public ServerOptionList() { 056 } 057 058 public ServerOptionList(List<String> auth, List<String> cand) { 059 authoritative.addAll(auth); 060 candidates.addAll(cand); 061 } 062 063 public void replace(String src, String dst) { 064 for (int i = 0; i < candidates.size(); i++) { 065 if (candidates.get(i).contains("://"+src)) { 066 candidates.set(i, candidates.get(i).replace("://"+src, "://"+dst)); 067 } 068 } 069 for (int i = 0; i < authoritative.size(); i++) { 070 if (authoritative.get(i).contains("://"+src)) { 071 authoritative.set(i, authoritative.get(i).replace("://"+src, "://"+dst)); 072 } 073 } 074 } 075 076 @Override 077 public String toString() { 078 return "auth = " + CommaSeparatedStringBuilder.join("|", authoritative)+ ", candidates=" + CommaSeparatedStringBuilder.join("|", candidates); 079 } 080 081 } 082 083 public ITerminologyClientFactory getFactory() { 084 return factory; 085 } 086 087 public interface ITerminologyClientFactory { 088 ITerminologyClient makeClient(String id, String url, String userAgent, ToolingClientLogger logger) throws URISyntaxException; 089 String getVersion(); 090 } 091 092 public class InternalLogEvent { 093 private boolean error; 094 private String message; 095 private String server; 096 private String vs; 097 private String systems; 098 private String choices; 099 private String context; 100 private String request; 101 protected InternalLogEvent(String message, String server, String vs, String systems, String choices) { 102 super(); 103 this.message = message; 104 this.server = server; 105 this.vs = vs; 106 this.systems = systems; 107 this.choices = choices; 108 } 109 protected InternalLogEvent(String message, String ctxt, String request) { 110 super(); 111 error = true; 112 this.message = message; 113 this.context = ctxt; 114 this.request = request; 115 } 116 public String getMessage() { 117 return message; 118 } 119 public String getVs() { 120 return vs; 121 } 122 public String getSystems() { 123 return systems; 124 } 125 public String getChoices() { 126 return choices; 127 } 128 public String getServer() { 129 return server; 130 } 131 public boolean isError() { 132 return error; 133 } 134 public String getContext() { 135 return context; 136 } 137 public String getRequest() { 138 return request; 139 } 140 } 141 142 public static final String UNRESOLVED_VALUESET = "--unknown--"; 143 144 private static final boolean IGNORE_TX_REGISTRY = false; 145 146 private ITerminologyClientFactory factory; 147 private String cacheId; 148 private List<TerminologyClientContext> serverList = new ArrayList<>(); // clients by server address 149 private Map<String, TerminologyClientContext> serverMap = new HashMap<>(); // clients by server address 150 private Map<String, ServerOptionList> resMap = new HashMap<>(); // client resolution list 151 private List<InternalLogEvent> internalLog = new ArrayList<>(); 152 protected Parameters expParameters; 153 154 private TerminologyCache cache; 155 156 private File cacheFile; 157 private String usage; 158 159 private String monitorServiceURL; 160 161 private boolean useEcosystem; 162 163 private ILoggingService logger; 164 165 private int ecosystemfailCount; 166 167 public TerminologyClientManager(ITerminologyClientFactory factory, String cacheId, ILoggingService logger) { 168 super(); 169 this.factory = factory; 170 this.cacheId = cacheId; 171 this.logger = logger; 172 implicitValueSets = new ImplicitValueSets(null); 173 } 174 175 public String getCacheId() { 176 return cacheId; 177 } 178 179 public void copy(TerminologyClientManager other) { 180 cacheId = other.cacheId; 181 serverList.addAll(other.serverList); 182 serverMap.putAll(other.serverMap); 183 resMap.putAll(other.resMap); 184 useEcosystem = other.useEcosystem; 185 monitorServiceURL = other.monitorServiceURL; 186 factory = other.factory; 187 usage = other.usage; 188 internalLog = other.internalLog; 189 } 190 191 192 public TerminologyClientContext chooseServer(ValueSet vs, Set<String> systems, boolean expand) throws TerminologyServiceException { 193 if (serverList.isEmpty()) { 194 return null; 195 } 196 if (systems.contains(UNRESOLVED_VALUESET) || systems.isEmpty()) { 197 return serverList.get(0); 198 } 199 200 List<ServerOptionList> choices = new ArrayList<>(); 201 for (String s : systems) { 202 choices.add(findServerForSystem(s, expand)); 203 } 204 205 // first we look for a server that's authoritative for all of them 206 for (ServerOptionList ol : choices) { 207 for (String s : ol.authoritative) { 208 boolean ok = true; 209 for (ServerOptionList t : choices) { 210 if (!t.authoritative.contains(s)) { 211 ok = false; 212 } 213 } 214 if (ok) { 215 log(vs, s, systems, choices, "Found authoritative server "+s); 216 return findClient(s, systems, expand); 217 } 218 } 219 } 220 221 // now we look for a server that's authoritative for one of them and a candidate for the others 222 for (ServerOptionList ol : choices) { 223 for (String s : ol.authoritative) { 224 boolean ok = true; 225 for (ServerOptionList t : choices) { 226 if (!t.authoritative.contains(s) && !t.candidates.contains(s)) { 227 ok = false; 228 } 229 } 230 if (ok) { 231 log(vs, s, systems, choices, "Found partially authoritative server "+s); 232 return findClient(s, systems, expand); 233 } 234 } 235 } 236 237 // now we look for a server that's a candidate for all of them 238 for (ServerOptionList ol : choices) { 239 for (String s : ol.candidates) { 240 boolean ok = true; 241 for (ServerOptionList t : choices) { 242 if (!t.candidates.contains(s)) { 243 ok = false; 244 } 245 } 246 if (ok) { 247 log(vs, s, systems, choices, "Found candidate server "+s); 248 return findClient(s, systems, expand); 249 } 250 } 251 } 252 253 for (String sys : systems) { 254 String uri = sys.contains("|") ? sys.substring(0, sys.indexOf("|")) : sys; 255 // this list is the list of code systems that have special handling on tx.fhir.org, and might not be resolved above. 256 // we don't want them to go to secondary servers (e.g. VSAC) by accident (they might go deliberately above) 257 if (Utilities.existsInList(uri, "http://unitsofmeasure.org", "http://loinc.org", "http://snomed.info/sct", 258 "http://www.nlm.nih.gov/research/umls/rxnorm", "http://hl7.org/fhir/sid/cvx", "urn:ietf:bcp:13", "urn:ietf:bcp:47", 259 "urn:ietf:rfc:3986", "http://www.ama-assn.org/go/cpt", "urn:oid:1.2.36.1.2001.1005.17", "urn:iso:std:iso:3166", 260 "http://varnomen.hgvs.org", "http://unstats.un.org/unsd/methods/m49/m49.htm", "urn:iso:std:iso:4217", 261 "http://hl7.org/fhir/sid/ndc", "http://fhir.ohdsi.org/CodeSystem/concepts", "http://fdasis.nlm.nih.gov", "https://www.usps.com/")) { 262 log(vs, serverList.get(0).getAddress(), systems, choices, "Use primary server for "+uri); 263 return serverList.get(0); 264 } 265 } 266 267 268 // no agreement - take the one that is must authoritative 269 Map<String, Integer> counts = new HashMap<>(); 270 for (ServerOptionList ol : choices) { 271 for (String s : ol.authoritative) { 272 counts.put(s, counts.getOrDefault(s, 0) + 1); 273 } 274 } 275 // Find the maximum 276 String max = counts.entrySet().stream() 277 .max(Map.Entry.comparingByValue()) 278 .map(Map.Entry::getKey) 279 .orElse(null); 280 if (max != null) { 281 log(vs, max, systems, choices, "Found most authoritative server "+max); 282 return findClient(max, systems, expand); 283 } 284 285 // no agreement? Then what we do depends 286 if (vs != null) { 287 if (vs.hasUserData(UserDataNames.render_external_link)) { 288 String el = vs.getUserString(UserDataNames.render_external_link); 289 if ("https://vsac.nlm.nih.gov".equals(el)) { 290 el = getMaster().getAddress(); 291 } 292 if (systems.size() == 1) { 293 log(vs, el, systems, choices, "Not handled by any servers. Using source @ '"+el+"'"); 294 } else { 295 log(vs, el, systems, choices, "Handled by multiple servers. Using source @ '"+el+"'"); 296 } 297 return findClient(el, systems, expand); 298 } else { 299 if (systems.size() == 1) { 300 log(vs, serverList.get(0).getAddress(), systems, choices, "System not handled by any servers. Using primary server"); 301 } else { 302 log(vs, serverList.get(0).getAddress(), systems, choices, "Systems handled by multiple servers. Using primary server"); 303 } 304 return findClient(serverList.get(0).getAddress(), systems, expand); 305 } 306 } else { 307 if (systems.size() == 1) { 308 log(vs, serverList.get(0).getAddress(), systems, choices, "System not handled by any servers. Using primary server"); 309 } else { 310 log(vs, serverList.get(0).getAddress(), systems, choices, "Systems handled by multiple servers. Using primary server"); 311 } 312 log(vs, serverList.get(0).getAddress(), systems, choices, "Fallback: primary server"); 313 return findClient(serverList.get(0).getAddress(), systems, expand); 314 } 315 } 316 317 public TerminologyClientContext chooseServer(String vs, boolean expand) throws TerminologyServiceException { 318 if (serverList.isEmpty()) { 319 return null; 320 } 321 if (IGNORE_TX_REGISTRY || !useEcosystem) { 322 return findClient(getMasterClient().getAddress(), null, expand); 323 } 324 String request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&valueSet="+Utilities.URLEncode(vs)); 325 if (usage != null) { 326 request = request + "&usage="+usage; 327 } 328 try { 329 JsonObject json = JsonParser.parseObjectFromUrl(request); 330 for (JsonObject item : json.getJsonObjects("authoritative")) { 331 return findClient(item.asString("url"), null, expand); 332 } 333 for (JsonObject item : json.getJsonObjects("candidates")) { 334 return findClient(item.asString("url"), null, expand); 335 } 336 } catch (Exception e) { 337 String msg = "Error resolving valueSet "+vs+": "+e.getMessage(); 338 if (!hasMessage(msg)) { 339 internalLog.add(new InternalLogEvent(msg, vs, request)); 340 } 341 logger.logDebugMessage(ILoggingService.LogCategory.TX, ExceptionUtils.getStackTrace(e)); 342 } 343 return null; 344 } 345 346 private void log(ValueSet vs, String server, Set<String> systems, List<ServerOptionList> choices, String message) { 347 String svs = (vs == null ? "null" : vs.getVersionedUrl()); 348 String sys = systems.isEmpty() ? "--" : systems.size() == 1 ? systems.iterator().next() : systems.toString(); 349 String sch = choices.isEmpty() ? "--" : choices.size() == 1 ? choices.iterator().next().toString() : choices.toString(); 350 internalLog.add(new InternalLogEvent(message, server, svs, sys, sch)); 351 } 352 353 private TerminologyClientContext findClient(String server, Set<String> systems, boolean expand) { 354 server = ManagedWebAccess.makeSecureRef(server); 355 TerminologyClientContext client = serverMap.get(server); 356 if (client == null) { 357 try { 358 client = new TerminologyClientContext(factory.makeClient("id"+(serverList.size()+1), server, getMasterClient().getUserAgent(), getMasterClient().getLogger()), cache, cacheId, false); 359 } catch (URISyntaxException | IOException e) { 360 throw new TerminologyServiceException(e); 361 } 362 //client.setTxCache(cache); 363 serverList.add(client); 364 serverMap.put(server, client); 365 } 366 client.seeUse(systems, expand ? TerminologyClientContextUseType.expand : TerminologyClientContextUseType.validate); 367 return client; 368 } 369 370 private ServerOptionList findServerForSystem(String s, boolean expand) throws TerminologyServiceException { 371 ServerOptionList serverList = resMap.get(s); 372 if (serverList == null) { 373 serverList = decideWhichServer(s); 374 // testing support 375 try { 376 serverList.replace("tx.fhir.org", host()); 377 } catch (MalformedURLException e) { 378 } 379 // resMap.put(s, serverList); 380 save(); 381 } 382 return serverList; 383 } 384 385 private String host() throws MalformedURLException { 386 URL url = new URL(getMasterClient().getAddress()); 387 if (url.getPort() > 0) { 388 return url.getHost()+":"+url.getPort(); 389 } else { 390 return url.getHost(); 391 } 392 } 393 394 private ServerOptionList decideWhichServer(String url) { 395 if (IGNORE_TX_REGISTRY || !useEcosystem) { 396 return new ServerOptionList(getMasterClient().getAddress()); 397 } 398 if (expParameters != null) { 399 if (!url.contains("|")) { 400 // the client hasn't specified an explicit version, but the expansion parameters might 401 for (ParametersParameterComponent p : expParameters.getParameter()) { 402 if (Utilities.existsInList(p.getName(), "system-version", "force-system-version") && p.hasValuePrimitive() && p.getValue().primitiveValue().startsWith(url+"|")) { 403 url = p.getValue().primitiveValue(); 404 } 405 } 406 } else { 407 // the expansion parameters might override the version 408 for (ParametersParameterComponent p : expParameters.getParameter()) { 409 if (Utilities.existsInList(p.getName(), "force-system-version") && p.hasValueCanonicalType() && p.getValue().primitiveValue().startsWith(url+"|")) { 410 url = p.getValue().primitiveValue(); 411 } 412 } 413 } 414 } 415 String request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&url="+Utilities.URLEncode(url)); 416 if (usage != null) { 417 request = request + "&usage="+usage; 418 } 419 try { 420 ServerOptionList ret = new ServerOptionList(); 421 JsonObject json = JsonParser.parseObjectFromUrl(request); 422 for (JsonObject item : json.getJsonObjects("authoritative")) { 423 ret.authoritative.add(item.asString("url")); 424 } 425 for (JsonObject item : json.getJsonObjects("candidates")) { 426 ret.candidates.add(item.asString("url")); 427 } 428 return ret; 429 } catch (Exception e) { 430 String msg = "Error resolving system "+url+": "+e.getMessage(); 431 if (!hasMessage(msg)) { 432 internalLog.add(new InternalLogEvent(msg, url, request)); 433 } 434 logger.logDebugMessage(ILoggingService.LogCategory.TX, ExceptionUtils.getStackTrace(e)); 435 } 436 return new ServerOptionList( getMasterClient().getAddress()); 437 438 } 439 440 private boolean hasMessage(String msg) { 441 for (InternalLogEvent log : internalLog) { 442 if (msg.equals(log.message)) { 443 return true; 444 } 445 } 446 return false; 447 } 448 449 public List<TerminologyClientContext> serverList() { 450 return serverList; 451 } 452 453 public boolean hasClient() { 454 return !serverList.isEmpty(); 455 } 456 457 public int getRetryCount() { 458 return hasClient() ? getMasterClient().getRetryCount() : 0; 459 } 460 461 public void setRetryCount(int value) { 462 if (hasClient()) { 463 getMasterClient().setRetryCount(value); 464 } 465 } 466 467 public void setUserAgent(String value) { 468 if (hasClient()) { 469 getMasterClient().setUserAgent(value); 470 } 471 } 472 473 public void setLogger(ToolingClientLogger txLog) { 474 if (hasClient()) { 475 getMasterClient().setLogger(txLog); 476 } 477 } 478 479 public TerminologyClientContext setMasterClient(ITerminologyClient client, boolean useEcosystem) throws IOException { 480 this.useEcosystem = useEcosystem; 481 TerminologyClientContext terminologyClientContext = new TerminologyClientContext(client, cache, cacheId,true); 482 serverList.clear(); 483 serverList.add(terminologyClientContext); 484 serverMap.put(client.getAddress(), terminologyClientContext); 485 monitorServiceURL = Utilities.pathURL(Utilities.getDirectoryForURL(client.getAddress()), "tx-reg"); 486 return terminologyClientContext; 487 } 488 489 public TerminologyClientContext getMaster() { 490 return serverList.isEmpty() ? null : serverList.get(0); 491 } 492 493 public ITerminologyClient getMasterClient() { 494 return serverList.isEmpty() ? null : serverList.get(0).getClient(); 495 } 496 497 public Map<String, TerminologyClientContext> serverMap() { 498 Map<String, TerminologyClientContext> map = new HashMap<>(); 499 for (TerminologyClientContext t : serverList) { 500 map.put(t.getClient().getAddress(), t); 501 } 502 return map; 503 } 504 505 506 public void setFactory(ITerminologyClientFactory factory) { 507 this.factory = factory; 508 } 509 510 public void setCache(TerminologyCache cache) { 511 this.cache = cache; 512 this.cacheFile = null; 513 514 if (cache != null && cache.getFolder() != null) { 515 try { 516 cacheFile = ManagedFileAccess.file(Utilities.path(cache.getFolder(), "system-map.json")); 517 if (cacheFile.exists()) { 518 JsonObject json = JsonParser.parseObject(cacheFile); 519 for (JsonObject pair : json.getJsonObjects("systems")) { 520 if (pair.has("server")) { 521 resMap.put(pair.asString("system"), new ServerOptionList(pair.asString("server"))); 522 } else { 523 resMap.put(pair.asString("system"), new ServerOptionList(pair.getStrings("authoritative"), pair.getStrings("candidates"))); 524 } 525 } 526 } 527 } catch (Exception e) { 528 e.printStackTrace(); 529 } 530 } 531 } 532 533 private void save() { 534 if (cacheFile != null && cache.getFolder() != null) { 535 JsonObject json = new JsonObject(); 536 for (String s : Utilities.sorted(resMap.keySet())) { 537 JsonObject si = new JsonObject(); 538 json.forceArray("systems").add(si); 539 si.add("system", s); 540 si.add("authoritative", resMap.get(s).authoritative); 541 si.add("candidates", resMap.get(s).candidates); 542 } 543 try { 544 JsonParser.compose(json, cacheFile, true); 545 } catch (IOException e) { 546 } 547 } 548 } 549 550 public List<TerminologyClientContext> getServerList() { 551 return serverList; 552 } 553 554 public Map<String, TerminologyClientContext> getServerMap() { 555 return serverMap; 556 } 557 558 public String getMonitorServiceURL() { 559 return monitorServiceURL; 560 } 561 562 public Parameters getExpansionParameters() { 563 return expParameters; 564 } 565 566 public void setExpansionParameters(Parameters expParameters) { 567 this.expParameters = expParameters; 568 implicitValueSets = new ImplicitValueSets(expParameters); 569 } 570 571 public String getUsage() { 572 return usage; 573 } 574 575 public void setUsage(String usage) { 576 this.usage = usage; 577 } 578 579 public SourcedValueSet findValueSetOnServer(String canonical) { 580 if (IGNORE_TX_REGISTRY || getMasterClient() == null) { 581 return null; 582 } 583 String request = null; 584 boolean isImplicit = false; 585 String iVersion = null; 586 if (implicitValueSets.isImplicitSCTValueSet(canonical)) { 587 isImplicit = true; 588 iVersion = canonical.substring(0, canonical.indexOf("?fhir_vs")); 589 if ("http://snomed.info/sct".equals(iVersion) && canonical.contains("|")) { 590 iVersion = canonical.substring(canonical.indexOf("|")+1); 591 } 592 iVersion = ValueSetUtilities.versionFromExpansionParams(expParameters, "http://snomed.info/sct", iVersion); 593 request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&url="+Utilities.URLEncode("http://snomed.info/sct"+(iVersion == null ? "": "|"+iVersion))); 594 } else if (implicitValueSets.isImplicitLoincValueSet(canonical)) { 595 isImplicit = true; 596 iVersion = null; 597 if (canonical.contains("|")) { 598 iVersion = canonical.substring(canonical.indexOf("|")+1); 599 } 600 iVersion = ValueSetUtilities.versionFromExpansionParams(expParameters, "http://loinc.org", iVersion); 601 request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&url="+Utilities.URLEncode("http://loinc.org"+(iVersion == null ? "": "|"+iVersion))); 602 } else { 603 request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&valueSet="+Utilities.URLEncode(canonical)); 604 } 605 String server = null; 606 try { 607 if (!useEcosystem) { 608 server = getMasterClient().getAddress(); 609 } else { 610 ecosystemfailCount = 0; 611 try { 612 if (usage != null) { 613 request = request + "&usage="+usage; 614 } 615 JsonObject json = JsonParser.parseObjectFromUrl(request); 616 for (JsonObject item : json.getJsonObjects("authoritative")) { 617 if (server == null) { 618 server = item.asString("url"); 619 } 620 } 621 for (JsonObject item : json.getJsonObjects("candidates")) { 622 if (server == null) { 623 server = item.asString("url"); 624 } 625 } 626 if (server == null) { 627 server = getMasterClient().getAddress(); 628 } 629 if (server.contains("://tx.fhir.org")) { 630 try { 631 server = server.replace("tx.fhir.org", host()); 632 } catch (MalformedURLException e) { 633 } 634 } 635 } catch (Exception e) { 636 // the ecosystem cal failed, so we're just going to fall back to 637 String msg = "Error resolving valueSet "+canonical+": "+e.getMessage(); 638 if (!hasMessage(msg)) { 639 internalLog.add(new InternalLogEvent(msg, canonical, request)); 640 } 641 logger.logDebugMessage(ILoggingService.LogCategory.TX, ExceptionUtils.getStackTrace(e)); 642 ecosystemfailCount++; 643 if (ecosystemfailCount > 3) { 644 useEcosystem = false; 645 } 646 server = getMasterClient().getAddress(); 647 } 648 } 649 TerminologyClientContext client = serverMap.get(server); 650 if (client == null) { 651 try { 652 client = new TerminologyClientContext(factory.makeClient("id"+(serverList.size()+1), ManagedWebAccess.makeSecureRef(server), getMasterClient().getUserAgent(), getMasterClient().getLogger()), cache, cacheId, false); 653 } catch (URISyntaxException | IOException e) { 654 throw new TerminologyServiceException(e); 655 } 656 657 serverList.add(client); 658 serverMap.put(server, client); 659 } 660 client.seeUse(canonical, TerminologyClientContextUseType.readVS); 661 String criteria = canonical.contains("|") ? 662 "?_format=json&url="+Utilities.URLEncode(canonical.substring(0, canonical.lastIndexOf("|")))+"&version="+Utilities.URLEncode(canonical.substring(canonical.lastIndexOf("|")+1)): 663 "?_format=json&url="+Utilities.URLEncode(canonical); 664 request = Utilities.pathURL(client.getAddress(), "ValueSet"+ criteria); 665 Bundle bnd = client.getClient().search("ValueSet", criteria); 666 String rid = null; 667 if (bnd.getEntry().size() == 0) { 668 if (isImplicit) { 669 // couldn't find it, but can we expand on it? 670 Parameters p= new Parameters(); 671 p.addParameter("url", new UriType(canonical)); 672 p.addParameter("count", 0); 673 p.addParameters(expParameters); 674 try { 675 ValueSet vs = client.getClient().expandValueset(null, p); 676 if (vs != null) { 677 return new SourcedValueSet(server, implicitValueSets.generateImplicitValueSet(canonical, iVersion)); 678 } 679 } catch (Exception e) { 680 return null; 681 } 682 } else { 683 return null; 684 } 685 } else if (bnd.getEntry().size() > 1) { 686 List<ValueSet> vslist = new ArrayList<>(); 687 for (BundleEntryComponent be : bnd.getEntry()) { 688 if (be.hasResource() && be.getResource() instanceof ValueSet) { 689 vslist.add((ValueSet) be.getResource()); 690 } 691 } 692 Collections.sort(vslist, new ValueSetUtilities.ValueSetSorter()); 693 rid = vslist.get(vslist.size()-1).getIdBase(); 694 } else { 695 if (bnd.getEntryFirstRep().hasResource() && bnd.getEntryFirstRep().getResource() instanceof ValueSet) { 696 rid = bnd.getEntryFirstRep().getResource().getIdBase(); 697 } 698 } 699 if (rid == null) { 700 return null; 701 } 702 ValueSet vs = (ValueSet) client.getClient().read("ValueSet", rid); 703 return new SourcedValueSet(server, vs); 704 } catch (Exception e) { 705 String msg = "Error resolving valueSet "+canonical+": "+e.getMessage(); 706 if (!hasMessage(msg)) { 707 internalLog.add(new InternalLogEvent(msg, canonical, request)); 708 } 709 logger.logDebugMessage(ILoggingService.LogCategory.TX, ExceptionUtils.getStackTrace(e)); 710 return null; 711 } 712 } 713 public SourcedCodeSystem findCodeSystemOnServer(String canonical) { 714 if (IGNORE_TX_REGISTRY || getMasterClient() == null || !useEcosystem) { 715 return null; 716 } 717 String request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&url="+Utilities.URLEncode(canonical)); 718 if (usage != null) { 719 request = request + "&usage="+usage; 720 } 721 String server = null; 722 try { 723 JsonObject json = JsonParser.parseObjectFromUrl(request); 724 for (JsonObject item : json.getJsonObjects("authoritative")) { 725 if (server == null) { 726 server = item.asString("url"); 727 } 728 } 729 for (JsonObject item : json.getJsonObjects("candidates")) { 730 if (server == null) { 731 server = item.asString("url"); 732 } 733 } 734 if (server == null) { 735 return null; 736 } 737 if (server.contains("://tx.fhir.org")) { 738 try { 739 server = server.replace("tx.fhir.org", host()); 740 } catch (MalformedURLException e) { 741 } 742 } 743 TerminologyClientContext client = serverMap.get(server); 744 if (client == null) { 745 try { 746 client = new TerminologyClientContext(factory.makeClient("id"+(serverList.size()+1), ManagedWebAccess.makeSecureRef(server), getMasterClient().getUserAgent(), getMasterClient().getLogger()), cache, cacheId, false); 747 } catch (URISyntaxException | IOException e) { 748 throw new TerminologyServiceException(e); 749 } 750 serverList.add(client); 751 serverMap.put(server, client); 752 } 753 client.seeUse(canonical, TerminologyClientContextUseType.readCS); 754 String criteria = canonical.contains("|") ? 755 "?_format=json&url="+Utilities.URLEncode(canonical.substring(0, canonical.lastIndexOf("|")))+(canonical.contains("|") ? "&version="+Utilities.URLEncode(canonical.substring(canonical.lastIndexOf("|")+1)) : "") : 756 "?_format=json&url="+Utilities.URLEncode(canonical); 757 request = Utilities.pathURL(client.getAddress(), "CodeSystem"+ criteria); 758 Bundle bnd = client.getClient().search("CodeSystem", criteria); 759 String rid = null; 760 if (bnd.getEntry().size() == 0) { 761 return null; 762 } else if (bnd.getEntry().size() > 1) { 763 List<CodeSystem> cslist = new ArrayList<>(); 764 for (BundleEntryComponent be : bnd.getEntry()) { 765 if (be.hasResource() && be.getResource() instanceof CodeSystem) { 766 cslist.add((CodeSystem) be.getResource()); 767 } 768 } 769 Collections.sort(cslist, new CodeSystemUtilities.CodeSystemSorter()); 770 rid = cslist.get(cslist.size()-1).getIdBase(); 771 } else { 772 if (bnd.getEntryFirstRep().hasResource() && bnd.getEntryFirstRep().getResource() instanceof CodeSystem) { 773 rid = bnd.getEntryFirstRep().getResource().getIdBase(); 774 } 775 } 776 if (rid == null) { 777 return null; 778 } 779 CodeSystem vs = (CodeSystem) client.getClient().read("CodeSystem", rid); 780 return new SourcedCodeSystem(server, vs); 781 } catch (Exception e) { 782 String msg = "Error resolving CodeSystem "+canonical+": "+e.getMessage(); 783 if (!hasMessage(msg)) { 784 internalLog.add(new InternalLogEvent(msg, canonical, request)); 785 } 786 logger.logDebugMessage(ILoggingService.LogCategory.TX, ExceptionUtils.getStackTrace(e)); 787 return null; 788 } 789 } 790 791 public boolean supportsSystem(String system) throws IOException { 792 for (TerminologyClientContext client : serverList) { 793 if (client.supportsSystem(system)) { 794 return true; 795 } 796 } 797 return false; 798 } 799 800 public List<InternalLogEvent> getInternalLog() { 801 return internalLog; 802 } 803 804 public ILoggingService getLogger() { 805 return logger; 806 } 807 808 public void setLogger(ILoggingService logger) { 809 this.logger = logger; 810 } 811 812 813}