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