All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hl7.fhir.r5.terminologies.client.TerminologyClientManager Maven / Gradle / Ivy

package org.hl7.fhir.r5.terminologies.client;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.ILoggingService;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.TerminologyClientContextUseType;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedCodeSystem;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet;
import org.hl7.fhir.r5.utils.UserDataNames;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
import org.hl7.fhir.utilities.ToolingClientLogger;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.parser.JsonParser;

@MarkedToMoveToAdjunctPackage
public class TerminologyClientManager {
  public class ServerOptionList {
    private List authoritative = new ArrayList();
    private List candidates = new ArrayList();
    
    public ServerOptionList(String address) {
      candidates.add(address);
    }
    
    public ServerOptionList() {
    }

    public ServerOptionList(List auth, List cand) {
      authoritative.addAll(auth);
      candidates.addAll(cand);
    }

    public void replace(String src, String dst) {
      for (int i = 0; i < candidates.size(); i++) {
        if (candidates.get(i).contains("://"+src)) {
          candidates.set(i, candidates.get(i).replace("://"+src, "://"+dst));
        }
      }
      for (int i = 0; i < authoritative.size(); i++) {
        if (authoritative.get(i).contains("://"+src)) {
          authoritative.set(i, authoritative.get(i).replace("://"+src, "://"+dst));
        }
      }      
    }

    @Override
    public String toString() {
      return "auth = " + CommaSeparatedStringBuilder.join("|", authoritative)+ ", candidates=" + CommaSeparatedStringBuilder.join("|", candidates);
    }    
    
  }

  public ITerminologyClientFactory getFactory() {
    return factory;
  }

  public interface ITerminologyClientFactory {
    ITerminologyClient makeClient(String id, String url, String userAgent, ToolingClientLogger logger) throws URISyntaxException;
    String getVersion();
  }
  
  public class InternalLogEvent {
    private boolean error;
    private String message;
    private String server;
    private String vs;
    private String systems;
    private String choices;
    private String context;
    private String request;
    protected InternalLogEvent(String message, String server, String vs, String systems, String choices) {
      super();
      this.message = message;
      this.server = server;
      this.vs = vs;
      this.systems = systems;
      this.choices = choices;
    }
    protected InternalLogEvent(String message, String ctxt, String request) {
      super();
      error = true;
      this.message = message;
      this.context = ctxt;
      this.request = request;
    }
    public String getMessage() {
      return message;
    }
    public String getVs() {
      return vs;
    }
    public String getSystems() {
      return systems;
    }
    public String getChoices() {
      return choices;
    }
    public String getServer() {
      return server;
    }
    public boolean isError() {
      return error;
    }
    public String getContext() {
      return context;
    }
    public String getRequest() {
      return request;
    }
  }

  public static final String UNRESOLVED_VALUESET = "--unknown--";

  private static final boolean IGNORE_TX_REGISTRY = false;
  
  private ITerminologyClientFactory factory;
  private String cacheId;
  private List serverList = new ArrayList<>(); // clients by server address
  private Map serverMap = new HashMap<>(); // clients by server address
  private Map resMap = new HashMap<>(); // client resolution list
  private List internalLog = new ArrayList<>();
  protected Parameters expParameters;

  private TerminologyCache cache;

  private File cacheFile;
  private String usage;

  private String monitorServiceURL;

  private boolean useEcosystem;

  private ILoggingService logger;

  private int ecosystemfailCount;

  public TerminologyClientManager(ITerminologyClientFactory factory, String cacheId, ILoggingService logger) {
    super();
    this.factory = factory;
    this.cacheId = cacheId;
    this.logger = logger;
  }
  
  public String getCacheId() {
    return cacheId; 
  }
  
  public void copy(TerminologyClientManager other) {
    cacheId = other.cacheId;  
    serverList.addAll(other.serverList);
    serverMap.putAll(other.serverMap);
    resMap.putAll(other.resMap);
    useEcosystem = other.useEcosystem;
    monitorServiceURL = other.monitorServiceURL;
    factory = other.factory;
    usage = other.usage;
    internalLog = other.internalLog;
  }


  public TerminologyClientContext chooseServer(ValueSet vs, Set systems, boolean expand) throws TerminologyServiceException {
    if (serverList.isEmpty()) {
      return null;
    }
    if (systems.contains(UNRESOLVED_VALUESET) || systems.isEmpty()) {
      return serverList.get(0);
    }
    
    List choices = new ArrayList<>();
    for (String s : systems) {
      choices.add(findServerForSystem(s, expand));
    }    
    
    // first we look for a server that's authoritative for all of them
    for (ServerOptionList ol : choices) {
      for (String s : ol.authoritative) {
        boolean ok = true;
        for (ServerOptionList t : choices) {
          if (!t.authoritative.contains(s)) {
            ok = false;
          }
        }
        if (ok) {
          log(vs, s, systems, choices, "Found authoritative server "+s);
          return findClient(s, systems, expand);
        }
      }
    }
    
    // now we look for a server that's authoritative for one of them and a candidate for the others
    for (ServerOptionList ol : choices) {
      for (String s : ol.authoritative) {
        boolean ok = true;
        for (ServerOptionList t : choices) {
          if (!t.authoritative.contains(s) && !t.candidates.contains(s)) {
            ok = false;
          }
        }
        if (ok) {
          log(vs, s, systems, choices, "Found partially authoritative server "+s);
          return findClient(s, systems, expand);
        }
      }
    }

    // now we look for a server that's a candidate for all of them
    for (ServerOptionList ol : choices) {
      for (String s : ol.candidates) {
        boolean ok = true;
        for (ServerOptionList t : choices) {
          if (!t.candidates.contains(s)) {
            ok = false;
          }
        }
        if (ok) {
          log(vs, s, systems, choices, "Found candidate server "+s);
          return findClient(s, systems, expand);
        }
      }
    }
    
    for (String sys : systems) {
      String uri = sys.contains("|") ? sys.substring(0, sys.indexOf("|")) : sys;
      // this list is the list of code systems that have special handling on tx.fhir.org, and might not be resolved above.
      // we don't want them to go to secondary servers (e.g. VSAC) by accident (they might go deliberately above)
      if (Utilities.existsInList(uri, "http://unitsofmeasure.org", "http://loinc.org", "http://snomed.info/sct",
          "http://www.nlm.nih.gov/research/umls/rxnorm", "http://hl7.org/fhir/sid/cvx", "urn:ietf:bcp:13", "urn:ietf:bcp:47",
          "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", 
          "http://varnomen.hgvs.org", "http://unstats.un.org/unsd/methods/m49/m49.htm", "urn:iso:std:iso:4217", 
          "http://hl7.org/fhir/sid/ndc", "http://fhir.ohdsi.org/CodeSystem/concepts", "http://fdasis.nlm.nih.gov", "https://www.usps.com/")) {
        log(vs, serverList.get(0).getAddress(), systems, choices, "Use primary server for "+uri);
        return serverList.get(0);
      }
    }

    // no agreement? Then what we do depends     
    if (vs != null) {
      if (vs.hasUserData(UserDataNames.render_external_link)) {
        String el = vs.getUserString(UserDataNames.render_external_link);
        if ("https://vsac.nlm.nih.gov".equals(el)) {
          el = getMaster().getAddress();
        }
        if (systems.size() == 1) {
          log(vs, el, systems, choices, "Not handled by any servers. Using source @ '"+el+"'");
        } else {
          log(vs, el, systems, choices, "Handled by multiple servers. Using source @ '"+el+"'");
        }        
        return findClient(el, systems, expand);
      } else {
        if (systems.size() == 1) {
          log(vs, serverList.get(0).getAddress(), systems, choices, "System not handled by any servers. Using primary server");
        } else {
          log(vs, serverList.get(0).getAddress(), systems, choices, "Systems handled by multiple servers. Using primary server");
        }
        return findClient(serverList.get(0).getAddress(), systems, expand);
      }      
    } else {
      if (systems.size() == 1) {
        log(vs, serverList.get(0).getAddress(), systems, choices, "System not handled by any servers. Using primary server");
      } else {
        log(vs, serverList.get(0).getAddress(), systems, choices, "Systems handled by multiple servers. Using primary server");
      }
      log(vs, serverList.get(0).getAddress(), systems, choices, "Fallback: primary server");
      return findClient(serverList.get(0).getAddress(), systems, expand);
    }
  }

  public TerminologyClientContext chooseServer(String vs, boolean expand) throws TerminologyServiceException {
    if (serverList.isEmpty()) {
      return null;
    }
    if (IGNORE_TX_REGISTRY || !useEcosystem) {
      return findClient(getMasterClient().getAddress(), null, expand);
    }
    String request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&valueSet="+Utilities.URLEncode(vs));
    if (usage != null) {
      request = request + "&usage="+usage;
    } 
    try {
      JsonObject json = JsonParser.parseObjectFromUrl(request);
      for (JsonObject item : json.getJsonObjects("authoritative")) {
        return findClient(item.asString("url"), null, expand);
      }
      for (JsonObject item : json.getJsonObjects("candidates")) {
        return findClient(item.asString("url"), null, expand);
      }
    } catch (Exception e) {
      String msg = "Error resolving valueSet "+vs+": "+e.getMessage();
      if (!hasMessage(msg)) {
        internalLog.add(new InternalLogEvent(msg, vs, request));
      }
      if (logger.isDebugLogging()) {
        e.printStackTrace();
      }
    }
    return null; 
  }

  private void log(ValueSet vs, String server, Set systems, List choices, String message) {
    String svs = (vs == null ? "null" : vs.getVersionedUrl());
    String sys = systems.isEmpty() ? "--" : systems.size() == 1 ? systems.iterator().next() : systems.toString();
    String sch = choices.isEmpty() ? "--" : choices.size() == 1 ? choices.iterator().next().toString() : choices.toString();
    internalLog.add(new InternalLogEvent(message, server, svs, sys, sch));
  }

  private TerminologyClientContext findClient(String server, Set systems, boolean expand) {
    TerminologyClientContext client = serverMap.get(server);
    if (client == null) {
      try {
        client = new TerminologyClientContext(factory.makeClient("id"+(serverList.size()+1), server, getMasterClient().getUserAgent(), getMasterClient().getLogger()), cacheId, false);
      } catch (URISyntaxException e) {
        throw new TerminologyServiceException(e);
      }
      client.setTxCache(cache);
      serverList.add(client);
      serverMap.put(server, client);
    }
    client.seeUse(systems, expand ? TerminologyClientContextUseType.expand : TerminologyClientContextUseType.validate);
    return client;
  }

  private ServerOptionList findServerForSystem(String s, boolean expand) throws TerminologyServiceException {
    ServerOptionList serverList = resMap.get(s);
    if (serverList == null) {
      serverList = decideWhichServer(s);
      // testing support
      try {
        serverList.replace("tx.fhir.org", host());
      } catch (MalformedURLException e) {
      }
      // resMap.put(s, serverList);
      save();
    }
    return serverList;
  }

  private String host() throws MalformedURLException {
    URL url = new URL(getMasterClient().getAddress());
    if (url.getPort() > 0) {
      return url.getHost()+":"+url.getPort();
    } else {
      return url.getHost();
    }
  }

  private ServerOptionList decideWhichServer(String url) {
    if (IGNORE_TX_REGISTRY || !useEcosystem) {
      return new ServerOptionList(getMasterClient().getAddress());
    }
    if (expParameters != null) {
      if (!url.contains("|")) {
        // the client hasn't specified an explicit version, but the expansion parameters might
        for (ParametersParameterComponent p : expParameters.getParameter()) {
          if (Utilities.existsInList(p.getName(), "system-version", "force-system-version") && p.hasValuePrimitive() && p.getValue().primitiveValue().startsWith(url+"|")) {
            url = p.getValue().primitiveValue();
          }
        }
      } else {
        // the expansion parameters might override the version
        for (ParametersParameterComponent p : expParameters.getParameter()) {
          if (Utilities.existsInList(p.getName(), "force-system-version") && p.hasValueCanonicalType() && p.getValue().primitiveValue().startsWith(url+"|")) {
            url = p.getValue().primitiveValue();
          }
        }
      }
    }
    String request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&url="+Utilities.URLEncode(url));
    if (usage != null) {
      request = request + "&usage="+usage;
    } 
    try {
      ServerOptionList ret = new ServerOptionList();
      JsonObject json = JsonParser.parseObjectFromUrl(request);
      for (JsonObject item : json.getJsonObjects("authoritative")) {
        ret.authoritative.add(item.asString("url"));
      }
      for (JsonObject item : json.getJsonObjects("candidates")) {
        ret.candidates.add(item.asString("url"));
      }
      return ret;
    } catch (Exception e) {
      String msg = "Error resolving system "+url+": "+e.getMessage();
      if (!hasMessage(msg)) {
        internalLog.add(new InternalLogEvent(msg, url, request));
      }
      if (logger.isDebugLogging()) {
        e.printStackTrace();
      }
    }
    return new ServerOptionList( getMasterClient().getAddress());
    
  }

  private boolean hasMessage(String msg) {
    for (InternalLogEvent log : internalLog) {
      if (msg.equals(log.message)) {
        return true;
      }
    }
    return false;
  }

  public List serverList() {
    return serverList;
  }
  
  public boolean hasClient() {
    return !serverList.isEmpty();
  }

  public int getRetryCount() {
    return hasClient() ? getMasterClient().getRetryCount() : 0;
  }

  public void setRetryCount(int value) {
    if (hasClient()) {
      getMasterClient().setRetryCount(value);
    }
  }

  public void setUserAgent(String value) {
    if (hasClient()) {
      getMasterClient().setUserAgent(value);
    }
  }

  public void setLogger(ToolingClientLogger txLog) {
    if (hasClient()) {
      getMasterClient().setLogger(txLog);
    }
  }

  public TerminologyClientContext setMasterClient(ITerminologyClient client, boolean useEcosystem) {
    this.useEcosystem = useEcosystem;
    TerminologyClientContext details = new TerminologyClientContext(client, cacheId, true);
    details.setTxCache(cache);
    serverList.clear();
    serverList.add(details);
    serverMap.put(client.getAddress(), details);  
    monitorServiceURL = Utilities.pathURL(Utilities.getDirectoryForURL(client.getAddress()), "tx-reg");
    return details;
  }
  
  public TerminologyClientContext getMaster() {
    return serverList.isEmpty() ? null : serverList.get(0);
  }

  public ITerminologyClient getMasterClient() {
    return serverList.isEmpty() ? null : serverList.get(0).getClient();
  }

  public Map serverMap() {
    Map map = new HashMap<>();
    for (TerminologyClientContext t : serverList) {
      map.put(t.getClient().getAddress(), t);
    }
    return map;
  }


  public void setFactory(ITerminologyClientFactory factory) {
    this.factory = factory;    
  }

  public void setCache(TerminologyCache cache) {
    this.cache = cache;
    this.cacheFile = null;

    if (cache != null && cache.getFolder() != null) {
      try {
        cacheFile = ManagedFileAccess.file(Utilities.path(cache.getFolder(), "system-map.json"));
        if (cacheFile.exists()) {
          JsonObject json = JsonParser.parseObject(cacheFile);
          for (JsonObject pair : json.getJsonObjects("systems")) {
            if (pair.has("server")) {
              resMap.put(pair.asString("system"), new ServerOptionList(pair.asString("server")));
            } else {
              resMap.put(pair.asString("system"), new ServerOptionList(pair.getStrings("authoritative"), pair.getStrings("candidates")));
            }
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  private void save() {
    if (cacheFile != null && cache.getFolder() != null) {
      JsonObject json = new JsonObject();
      for (String s : Utilities.sorted(resMap.keySet())) {
        JsonObject si = new JsonObject();
        json.forceArray("systems").add(si);
        si.add("system", s);
        si.add("authoritative", resMap.get(s).authoritative);
        si.add("candidates", resMap.get(s).candidates);
      }
      try {
        JsonParser.compose(json, cacheFile, true);
      } catch (IOException e) {
      }
    }
  }

  public List getServerList() {
    return serverList;
  }

  public Map getServerMap() {
    return serverMap;
  }

  public String getMonitorServiceURL() {
    return monitorServiceURL;
  }

  public Parameters getExpansionParameters() {
    return expParameters;
  }

  public void setExpansionParameters(Parameters expParameters) {
    this.expParameters = expParameters;
  }

  public String getUsage() {
    return usage;
  }

  public void setUsage(String usage) {
    this.usage = usage;
  }

  public SourcedValueSet findValueSetOnServer(String canonical) {
    if (IGNORE_TX_REGISTRY || getMasterClient() == null) {
      return null;
    }
    String request = null;
    boolean isImplicit = false;
    String iVersion = null;
    if (ValueSetUtilities.isImplicitSCTValueSet(canonical)) {
      isImplicit = true;
      iVersion = canonical.substring(0, canonical.indexOf("?fhir_vs"));
      if ("http://snomed.info/sct".equals(iVersion) && canonical.contains("|")) {
        iVersion = canonical.substring(canonical.indexOf("|")+1);
      } 
      iVersion = ValueSetUtilities.versionFromExpansionParams(expParameters, "http://snomed.info/sct", iVersion); 
      request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&url="+Utilities.URLEncode("http://snomed.info/sct"+(iVersion == null ? "": "|"+iVersion)));
    } else if (ValueSetUtilities.isImplicitLoincValueSet(canonical)) {
      isImplicit = true;
      iVersion = null;
      if (canonical.contains("|")) {
        iVersion = canonical.substring(canonical.indexOf("|")+1);
      } 
      iVersion = ValueSetUtilities.versionFromExpansionParams(expParameters, "http://loinc.org", iVersion); 
      request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&url="+Utilities.URLEncode("http://loinc.org"+(iVersion == null ? "": "|"+iVersion)));
    } else {
      request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&valueSet="+Utilities.URLEncode(canonical));
    }
    String server = null;
    try {
      if (!useEcosystem) {
        server = getMasterClient().getAddress();
      } else {
        ecosystemfailCount = 0; 
        try {
          if (usage != null) {
            request = request + "&usage="+usage;
          }
          JsonObject json = JsonParser.parseObjectFromUrl(request);
          for (JsonObject item : json.getJsonObjects("authoritative")) {
            if (server == null) {
              server = item.asString("url");
            }
          }
          for (JsonObject item : json.getJsonObjects("candidates")) {
            if (server == null) {
              server = item.asString("url");
            }
          }
          if (server == null) {
            server = getMasterClient().getAddress();
          }
          if (server.contains("://tx.fhir.org")) {
            try {
              server = server.replace("tx.fhir.org", host());
            } catch (MalformedURLException e) {
            }
          }
        } catch (Exception e) {
          // the ecosystem cal failed, so we're just going to fall back to 
          String msg = "Error resolving valueSet "+canonical+": "+e.getMessage();
          if (!hasMessage(msg)) {
            internalLog.add(new InternalLogEvent(msg, canonical, request));
          }
          if (logger.isDebugLogging()) {
            e.printStackTrace();
          }
          ecosystemfailCount++;
          if (ecosystemfailCount > 3) {
            useEcosystem = false;
          }
          server = getMasterClient().getAddress();
        }
      }
      TerminologyClientContext client = serverMap.get(server);
      if (client == null) {
        try {
          client = new TerminologyClientContext(factory.makeClient("id"+(serverList.size()+1), server, getMasterClient().getUserAgent(), getMasterClient().getLogger()), cacheId, false);
        } catch (URISyntaxException e) {
          throw new TerminologyServiceException(e);
        }
        client.setTxCache(cache);
        serverList.add(client);
        serverMap.put(server, client);
      }
      client.seeUse(canonical, TerminologyClientContextUseType.readVS);
      String criteria = canonical.contains("|") ? 
          "?_format=json&url="+Utilities.URLEncode(canonical.substring(0, canonical.lastIndexOf("|")))+"&version="+Utilities.URLEncode(canonical.substring(canonical.lastIndexOf("|")+1)): 
            "?_format=json&url="+Utilities.URLEncode(canonical);
      request = Utilities.pathURL(client.getAddress(), "ValueSet"+ criteria);
      Bundle bnd = client.getClient().search("ValueSet", criteria);
      String rid = null;
      if (bnd.getEntry().size() == 0) {
        if (isImplicit) {
          // couldn't find it, but can we expand on it? 
          Parameters p= new Parameters();
          p.addParameter("url", new UriType(canonical));
          p.addParameter("count", 0);
          p.addParameters(expParameters);
          try {
            ValueSet vs = client.getClient().expandValueset(null, p);
            if (vs != null) {
              return new SourcedValueSet(server, ValueSetUtilities.makeImplicitValueSet(canonical, iVersion));
            }
          } catch (Exception e) {
            return null;
          }
        } else {
          return null;
        }
      } else if (bnd.getEntry().size() > 1) {
        List vslist = new ArrayList<>();
        for (BundleEntryComponent be : bnd.getEntry()) {
          if (be.hasResource() && be.getResource() instanceof ValueSet) {
            vslist.add((ValueSet) be.getResource());
          }
        }
        Collections.sort(vslist, new ValueSetUtilities.ValueSetSorter());
        rid = vslist.get(vslist.size()-1).getIdBase();
      } else {
        if (bnd.getEntryFirstRep().hasResource() && bnd.getEntryFirstRep().getResource() instanceof ValueSet) {
          rid = bnd.getEntryFirstRep().getResource().getIdBase();
        }
      }
      if (rid == null) {
        return null;
      }
      ValueSet vs = (ValueSet) client.getClient().read("ValueSet", rid);
      return new SourcedValueSet(server, vs);
    } catch (Exception e) {
      String msg = "Error resolving valueSet "+canonical+": "+e.getMessage();
      if (!hasMessage(msg)) {
        internalLog.add(new InternalLogEvent(msg, canonical, request));
      }
      if (logger.isDebugLogging()) {
        e.printStackTrace();
      }
      return null;
    }
  }
  public SourcedCodeSystem findCodeSystemOnServer(String canonical) {
    if (IGNORE_TX_REGISTRY || getMasterClient() == null || !useEcosystem) {
      return null;
    }
    String request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&url="+Utilities.URLEncode(canonical));
    if (usage != null) {
      request = request + "&usage="+usage;
    }
    String server = null;
    try {
      JsonObject json = JsonParser.parseObjectFromUrl(request);
      for (JsonObject item : json.getJsonObjects("authoritative")) {
        if (server == null) {
          server = item.asString("url");
        }
      }
      for (JsonObject item : json.getJsonObjects("candidates")) {
        if (server == null) {
          server = item.asString("url");
        }
      }
      if (server == null) {
        return null;
      }
      if (server.contains("://tx.fhir.org")) {
        try {
          server = server.replace("tx.fhir.org", host());
        } catch (MalformedURLException e) {
        }
      }
      TerminologyClientContext client = serverMap.get(server);
      if (client == null) {
        try {
          client = new TerminologyClientContext(factory.makeClient("id"+(serverList.size()+1), server, getMasterClient().getUserAgent(), getMasterClient().getLogger()), cacheId, false);
        } catch (URISyntaxException e) {
          throw new TerminologyServiceException(e);
        }
        client.setTxCache(cache);
        serverList.add(client);
        serverMap.put(server, client);
      }
      client.seeUse(canonical, TerminologyClientContextUseType.readCS);
      String criteria = canonical.contains("|") ? 
          "?_format=json&url="+Utilities.URLEncode(canonical.substring(0, canonical.lastIndexOf("|")))+(canonical.contains("|") ? "&version="+Utilities.URLEncode(canonical.substring(canonical.lastIndexOf("|")+1)) : "") : 
            "?_format=json&url="+Utilities.URLEncode(canonical);
      request = Utilities.pathURL(client.getAddress(), "CodeSystem"+ criteria);
      Bundle bnd = client.getClient().search("CodeSystem", criteria);
      String rid = null;
      if (bnd.getEntry().size() == 0) {
        return null;
      } else if (bnd.getEntry().size() > 1) {
        List cslist = new ArrayList<>();
        for (BundleEntryComponent be : bnd.getEntry()) {
          if (be.hasResource() && be.getResource() instanceof CodeSystem) {
            cslist.add((CodeSystem) be.getResource());
          }
        }
        Collections.sort(cslist, new CodeSystemUtilities.CodeSystemSorter());
        rid = cslist.get(cslist.size()-1).getIdBase();
      } else {
        if (bnd.getEntryFirstRep().hasResource() && bnd.getEntryFirstRep().getResource() instanceof CodeSystem) {
          rid = bnd.getEntryFirstRep().getResource().getIdBase();
        }
      }
      if (rid == null) {
        return null;
      }
      CodeSystem vs = (CodeSystem) client.getClient().read("CodeSystem", rid);
      return new SourcedCodeSystem(server, vs);
    } catch (Exception e) {
      String msg = "Error resolving CodeSystem "+canonical+": "+e.getMessage();
      if (!hasMessage(msg)) {
        internalLog.add(new InternalLogEvent(msg, canonical, request));
      }
      if (logger.isDebugLogging()) {
        e.printStackTrace();
      }
      return null;
    }
  }

  public boolean supportsSystem(String system) throws IOException {
    for (TerminologyClientContext client : serverList) {
      if (client.supportsSystem(system)) {
        return true;
      }
    }
    return false;
  }

  public List getInternalLog() {
    return internalLog;
  }

  public ILoggingService getLogger() {
    return logger;
  }

  public void setLogger(ILoggingService logger) {
    this.logger = logger;
  }

  
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy