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