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