From 60bf358bfa978b95b39041d8412f92ea401cefa9 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 23 Apr 2024 08:34:25 +1000 Subject: [PATCH] Look up CodeSystem from terminology server + don't use tx-registry when manual terminology server is set --- RELEASE_NOTES.md | 18 ++- .../fhir/r5/context/BaseWorkerContext.java | 23 ++++ .../fhir/r5/context/SimpleWorkerContext.java | 8 +- .../r5/terminologies/CodeSystemUtilities.java | 33 ++++++ .../client/TerminologyClientContext.java | 14 ++- .../client/TerminologyClientManager.java | 93 +++++++++++++++- .../utilities/TerminologyCache.java | 105 ++++++++++++++++++ .../r5/context/SimpleWorkerContextTests.java | 6 +- .../src/main/resources/Messages.properties | 2 +- .../fhir/validation/NativeHostServices.java | 4 +- .../hl7/fhir/validation/ValidationEngine.java | 25 +++-- .../fhir/validation/cli/model/CliContext.java | 13 +++ .../cli/services/ValidationService.java | 7 +- .../hl7/fhir/validation/cli/utils/Params.java | 4 +- .../instance/type/ValueSetValidator.java | 4 + .../tests/utilities/TestUtilities.java | 4 +- .../4.0.1/cs-externals.json | 8 ++ .../5.0.0/cs-externals.json | 6 + 18 files changed, 345 insertions(+), 32 deletions(-) create mode 100644 org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/cs-externals.json create mode 100644 org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/cs-externals.json diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7b06c6ab5..32292b345 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,7 +1,21 @@ ## Validator Changes -* no changes +* fix NPE loading resources +* Don't enforce ids on elements when processing CDA +* Send supplements to tx server +* fix bug processing code bindings when value sets are complex (multiple filters) +* fix spelling of heirarchy +* Look up CodeSystem from terminology server +* Don't use tx-registry when manual terminology server is set ## Other code changes -* no changes \ No newline at end of file +* More work on WHO language support ($1592) +* allow validation message to have count +* render versions in profile links when necessary +* rework OID handling for better OID -> CodeSystem resolution +* fix up vsac importer for changes to client +* don't send xhtml for tx operations +* FHIRPath: Backport the defineVariable code to the R4 and R4B fhirpath implementations +* FHIRPath: Remove the alias/aliasAs custom functions (use standard defineVariable now) +* Bump lombok (#1603) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java index 049d754b1..57de55ec0 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java @@ -125,6 +125,7 @@ import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.Termi import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.CacheToken; +import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedCodeSystem; import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet; import org.hl7.fhir.r5.terminologies.validation.VSCheckerException; import org.hl7.fhir.r5.terminologies.validation.ValueSetValidator; @@ -3234,6 +3235,28 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte cacheResource(svs.getVs()); return (T) svs.getVs(); } + } else if (class_ == CodeSystem.class) { + SourcedCodeSystem scs = null; + if (txCache.hasCodeSystem(canonical)) { + scs = txCache.getCodeSystem(canonical); + } else { + scs = terminologyClientManager.findCodeSystemOnServer(canonical); + txCache.cacheCodeSystem(canonical, scs); + } + if (scs != null) { + String web = ToolingExtensions.readStringExtension(scs.getCs(), ToolingExtensions.EXT_WEB_SOURCE); + if (web == null) { + web = Utilities.pathURL(scs.getServer(), "ValueSet", scs.getCs().getIdBase()); + } + scs.getCs().setWebPath(web); + scs.getCs().setUserData("External.Link", scs.getServer()); // so we can render it differently + } + if (scs == null) { + return null; + } else { + cacheResource(scs.getCs()); + return (T) scs.getCs(); + } } else { throw new Error("Not supported"); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java index bff32155b..66581d063 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java @@ -332,12 +332,12 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon loadBytes(name, stream); } - public void connectToTSServer(ITerminologyClientFactory factory, ITerminologyClient client) { + public void connectToTSServer(ITerminologyClientFactory factory, ITerminologyClient client, boolean useEcosystem) { terminologyClientManager.setFactory(factory); if (txLog == null) { txLog = client.getLogger(); } - TerminologyClientContext tcc = terminologyClientManager.setMasterClient(client); + TerminologyClientContext tcc = terminologyClientManager.setMasterClient(client, useEcosystem); txLog("Connect to "+client.getAddress()); try { tcc.initialize(); @@ -357,7 +357,7 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon } } - public void connectToTSServer(ITerminologyClientFactory factory, String address, String software, String log) { + public void connectToTSServer(ITerminologyClientFactory factory, String address, String software, String log, boolean useEcosystem) { try { terminologyClientManager.setFactory(factory); if (log != null && (log.endsWith(".htm") || log.endsWith(".html"))) { @@ -369,7 +369,7 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon // txFactory.makeClient("Tx-Server", txServer, "fhir/publisher", null) // terminologyClientManager.setLogger(txLog); // terminologyClientManager.setUserAgent(userAgent); - connectToTSServer(factory, client); + connectToTSServer(factory, client, useEcosystem); } catch (Exception e) { e.printStackTrace(); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java index bdca7a290..348d7e8cf 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java @@ -71,9 +71,42 @@ import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.MarkDownProcessor; import org.hl7.fhir.utilities.StandardsStatus; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.VersionUtilities; public class CodeSystemUtilities extends TerminologyUtilities { + public static class CodeSystemSorter implements Comparator { + + @Override + public int compare(CodeSystem o1, CodeSystem o2) { + String url1 = o1.getUrl(); + String url2 = o2.getUrl(); + int c = compareString(url1, url2); + if (c == 0) { + String ver1 = o1.getVersion(); + String ver2 = o2.getVersion(); + c = VersionUtilities.compareVersions(ver1, ver2); + if (c == 0) { + String d1 = o1.getDateElement().asStringValue(); + String d2 = o2.getDateElement().asStringValue(); + c = compareString(url1, url2); + } + } + return c; + } + + private int compareString(String s1, String s2) { + if (s1 == null) { + return s2 == null ? 0 : 1; + } else { + return s1.compareTo(s2); + } + } + + } + + + public static class SystemReference { private String link; private String text; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientContext.java index 5bc24d3ea..a15139944 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientContext.java @@ -16,19 +16,26 @@ import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache; public class TerminologyClientContext { public enum TerminologyClientContextUseType { - expand, validate, readVS + expand, validate, readVS, readCS } public class TerminologyClientContextUseCount { private int expands; private int validates; private int readVS; - + private int readCS; + public int getReadVS() { return readVS; } public void setReadVS(int readVS) { this.readVS = readVS; } + public int getReadCS() { + return readCS; + } + public void setReadCS(int readCS) { + this.readCS = readCS; + } public int getExpands() { return expands; } @@ -94,6 +101,9 @@ public class TerminologyClientContext { case readVS: uc.readVS++; break; + case readCS: + uc.readCS++; + break; case validate: uc.validates++; break; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java index 322b1591d..f5c228f56 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientManager.java @@ -17,15 +17,18 @@ import org.hl7.fhir.exceptions.TerminologyServiceException; import org.hl7.fhir.r5.context.CanonicalResourceManager; 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.TerminologyCapabilities.TerminologyCapabilitiesCodeSystemComponent; import org.hl7.fhir.r5.model.TerminologyCapabilities; 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.client.TerminologyClientManager.ServerOptionList; 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.ToolingExtensions; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; @@ -100,6 +103,8 @@ public class TerminologyClientManager { private String monitorServiceURL; + private boolean useEcosystem; + public TerminologyClientManager(ITerminologyClientFactory factory, String cacheId) { super(); this.factory = factory; @@ -115,6 +120,7 @@ public class TerminologyClientManager { serverList.addAll(other.serverList); serverMap.putAll(other.serverMap); resMap.putAll(other.resMap); + useEcosystem = other.useEcosystem; monitorServiceURL = other.monitorServiceURL; factory = other.factory; usage = other.usage; @@ -238,7 +244,7 @@ public class TerminologyClientManager { } private ServerOptionList decideWhichServer(String url) { - if (IGNORE_TX_REGISTRY) { + if (IGNORE_TX_REGISTRY || !useEcosystem) { return new ServerOptionList(getMasterClient().getAddress()); } if (expParameters != null) { @@ -315,7 +321,8 @@ public class TerminologyClientManager { } } - public TerminologyClientContext setMasterClient(ITerminologyClient client) { + public TerminologyClientContext setMasterClient(ITerminologyClient client, boolean useEcosystem) { + this.useEcosystem = useEcosystem; TerminologyClientContext details = new TerminologyClientContext(client, cacheId, true); details.setTxCache(cache); serverList.clear(); @@ -419,7 +426,7 @@ public class TerminologyClientManager { } public SourcedValueSet findValueSetOnServer(String canonical) { - if (IGNORE_TX_REGISTRY || getMasterClient() == null) { + if (IGNORE_TX_REGISTRY || getMasterClient() == null || !useEcosystem) { return null; } String request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&valueSet="+Utilities.URLEncode(canonical)); @@ -498,6 +505,86 @@ public class TerminologyClientManager { } } + public SourcedCodeSystem findCodeSystemOnServer(String canonical) { + if (IGNORE_TX_REGISTRY || getMasterClient() == null || !useEcosystem) { + return null; + } + String request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&codeSystem="+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", new URL(getMasterClient().getAddress()).getHost()); + } 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("|")))+"&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) { + e.printStackTrace(); + String msg = "Error resolving valueSet "+canonical+": "+e.getMessage()+" ("+request+")"; + if (!internalLog.contains(msg)) { + internalLog.add(msg); + } + e.printStackTrace(); + return null; + } + } + public boolean supportsSystem(String system) throws IOException { for (TerminologyClientContext client : serverList) { if (client.supportsSystem(system)) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java index b4316ec0a..4de741209 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java @@ -83,6 +83,42 @@ import com.google.gson.JsonPrimitive; */ public class TerminologyCache { + public static class SourcedCodeSystem { + private String server; + private CodeSystem cs; + + public SourcedCodeSystem(String server, CodeSystem cs) { + super(); + this.server = server; + this.cs = cs; + } + public String getServer() { + return server; + } + public CodeSystem getCs() { + return cs; + } + } + + + public static class SourcedCodeSystemEntry { + private String server; + private String filename; + + public SourcedCodeSystemEntry(String server, String filename) { + super(); + this.server = server; + this.filename = filename; + } + public String getServer() { + return server; + } + public String getFilename() { + return filename; + } + } + + public static class SourcedValueSet { private String server; private ValueSet vs; @@ -253,6 +289,7 @@ public class TerminologyCache { private Map terminologyCapabilitiesCache = new HashMap<>(); private Map caches = new HashMap(); private Map vsCache = new HashMap<>(); + private Map csCache = new HashMap<>(); private Map serverMap = new HashMap<>(); @Getter @Setter private static boolean noCaching; @@ -320,12 +357,14 @@ public class TerminologyCache { // not useable after this is called caches.clear(); vsCache.clear(); + csCache.clear(); } private void clear() throws IOException { Utilities.clearDirectory(folder); caches.clear(); vsCache.clear(); + csCache.clear(); } public boolean hasCapabilityStatement(String address) { @@ -872,6 +911,22 @@ public class TerminologyCache { } catch (Exception e) { System.out.println("Error loading vs external cache: "+e.getMessage()); } + try { + File f = ManagedFileAccess.file(Utilities.path(folder, "cs-externals.json")); + if (f.exists()) { + org.hl7.fhir.utilities.json.model.JsonObject json = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(f); + for (JsonProperty p : json.getProperties()) { + if (p.getValue().isJsonNull()) { + csCache.put(p.getName(), null); + } else { + org.hl7.fhir.utilities.json.model.JsonObject j = p.getValue().asJsonObject(); + csCache.put(p.getName(), new SourcedCodeSystemEntry(j.asString("server"), j.asString("filename"))); + } + } + } + } catch (Exception e) { + System.out.println("Error loading vs external cache: "+e.getMessage()); + } } private String loadJS(JsonElement e) { @@ -978,6 +1033,10 @@ public class TerminologyCache { return vsCache.containsKey(canonical); } + public boolean hasCodeSystem(String canonical) { + return csCache.containsKey(canonical); + } + public SourcedValueSet getValueSet(String canonical) { SourcedValueSetEntry sp = vsCache.get(canonical); if (sp == null) { @@ -991,6 +1050,19 @@ public class TerminologyCache { } } + public SourcedCodeSystem getCodeSystem(String canonical) { + SourcedCodeSystemEntry sp = csCache.get(canonical); + if (sp == null) { + return null; + } else { + try { + return new SourcedCodeSystem(sp.getServer(), sp.getFilename() == null ? null : (CodeSystem) new JsonParser().parse(ManagedFileAccess.inStream(Utilities.path(folder, sp.getFilename())))); + } catch (Exception e) { + return null; + } + } + } + public void cacheValueSet(String canonical, SourcedValueSet svs) { if (canonical == null) { return; @@ -1024,6 +1096,39 @@ public class TerminologyCache { } } + public void cacheCodeSystem(String canonical, SourcedCodeSystem scs) { + if (canonical == null) { + return; + } + try { + if (scs == null) { + csCache.put(canonical, null); + } else { + String uuid = Utilities.makeUuidLC(); + String fn = "cs-"+uuid+".json"; + new JsonParser().compose(ManagedFileAccess.outStream(Utilities.path(folder, fn)), scs.getCs()); + csCache.put(canonical, new SourcedCodeSystemEntry(scs.getServer(), fn)); + } + org.hl7.fhir.utilities.json.model.JsonObject j = new org.hl7.fhir.utilities.json.model.JsonObject(); + for (String k : csCache.keySet()) { + SourcedCodeSystemEntry sve = csCache.get(k); + if (sve == null) { + j.add(k, new JsonNull()); + } else { + org.hl7.fhir.utilities.json.model.JsonObject e = new org.hl7.fhir.utilities.json.model.JsonObject(); + e.set("server", sve.getServer()); + if (sve.getFilename() != null) { + e.set("filename", sve.getFilename()); + } + j.add(k, e); + } + } + org.hl7.fhir.utilities.json.parser.JsonParser.compose(j, ManagedFileAccess.file(Utilities.path(folder, "cs-externals.json")), true); + } catch (Exception e) { + e.printStackTrace(); + } + } + public CacheToken generateSubsumesToken(ValidationOptions options, Coding parent, Coding child, Parameters expParameters) { try { CacheToken ct = new CacheToken(); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/SimpleWorkerContextTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/SimpleWorkerContextTests.java index 8e4523ea2..4dc9b0279 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/SimpleWorkerContextTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/SimpleWorkerContextTests.java @@ -97,7 +97,7 @@ public class SimpleWorkerContextTests { Mockito.doReturn(DUMMY_URL).when(terminologyClient).getAddress(); context.initTxCache(terminologyCache); context.expParameters = expParameters; - context.terminologyClientManager.setMasterClient(terminologyClient); + context.terminologyClientManager.setMasterClient(terminologyClient, false); context.txLog = txLog; } @@ -437,7 +437,7 @@ public class SimpleWorkerContextTests { Mockito.doReturn(terminologyCapabilities).when(terminologyCache).getTerminologyCapabilities(address); - context.connectToTSServer(new TerminologyClientR5Factory(), terminologyClient); + context.connectToTSServer(new TerminologyClientR5Factory(), terminologyClient, false); Mockito.verify(terminologyCache).getTerminologyCapabilities(address); Mockito.verify(terminologyClient).getCapabilitiesStatementQuick(); @@ -455,7 +455,7 @@ public class SimpleWorkerContextTests { Mockito.doReturn(terminologyCapabilities).when(terminologyClient).getTerminologyCapabilities(); Mockito.doReturn(capabilitiesStatement).when(terminologyClient).getCapabilitiesStatementQuick(); - context.connectToTSServer(new TerminologyClientR5Factory(), terminologyClient); + context.connectToTSServer(new TerminologyClientR5Factory(), terminologyClient, false); Mockito.verify(terminologyCache, times(0)).getTerminologyCapabilities(address); Mockito.verify(terminologyCache, times(0)).getCapabilityStatement(address); diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 3e749bcf2..18fc5c8e1 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -1125,7 +1125,7 @@ CODESYSTEM_PROPERTY_WRONG_TYPE = The property ''{0}'' has the invalid type ''{1} CODESYSTEM_DESIGNATION_DISP_CLASH_NO_LANG = The designation ''{0}'' has no use and no language, so is not differentiated from the base display (''{1}'') CODESYSTEM_DESIGNATION_DISP_CLASH_LANG = The designation ''{0}'' has no use and is in the same language (''{2}''), so is not differentiated from the base display (''{1}'') VALUESET_UNKNOWN_FILTER_PROPERTY = The property ''{0}'' is not known for the system ''{1}'', so may not be understood by the terminology ecosystem. Known properties for this system: {2} -VALUESET_UNKNOWN_FILTER_PROPERTY_NO_CS = No definition can be found for the system {1}, and the property ''{0}'' is not a generally known property, so the property might not be valid, or understood by the terminology ecosystem. In case it's useful, the list of generally known properties for all systems is {2} +VALUESET_UNKNOWN_FILTER_PROPERTY_NO_CS = No definition can be found for the system {1}, and the property ''{0}'' is not a generally known property, so the property might not be valid, or understood by the terminology ecosystem. In case it''s useful, the list of generally known properties for all systems is {2} VALUESET_BAD_FILTER_VALUE_BOOLEAN = The value for a filter based on property ''{0}'' must be either ''true'' or ''false'', not ''{1}'' VALUESET_BAD_FILTER_VALUE_CODE = The value for a filter based on property ''{0}'' must be a valid code, not ''{1}'' VALUESET_BAD_FILTER_VALUE_DATETIME = The value for a filter based on property ''{0}'' must be a valid date(/time), not ''{1}'' diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/NativeHostServices.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/NativeHostServices.java index a2c71126e..56077d420 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/NativeHostServices.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/NativeHostServices.java @@ -161,7 +161,7 @@ public class NativeHostServices { * @throws Exception */ public void connectToTxSvc(String txServer, String log) throws Exception { - validator.connectToTSServer(txServer, log, FhirPublication.R5); + validator.connectToTSServer(txServer, log, FhirPublication.R5, false); } /** @@ -171,7 +171,7 @@ public class NativeHostServices { * @throws Exception */ public void connectToTxSvc(String txServer, String log, String txCache) throws Exception { - validator.connectToTSServer(txServer, log, txCache, FhirPublication.R5); + validator.connectToTSServer(txServer, log, txCache, FhirPublication.R5, false); } /** diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index d76a5603a..29df53882 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -325,6 +325,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP private final String txServer; private final String txLog; private final FhirPublication txVersion; + private final boolean useEcosystem; @With private final TimeTracker timeTracker; @@ -348,10 +349,11 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP txVersion = null; timeTracker = null; canRunWithoutTerminologyServer = false; + useEcosystem = true; loggingService = new SystemOutLoggingService(); } - public ValidationEngineBuilder(String terminologyCachePath, String userAgent, String version, String txServer, String txLog, FhirPublication txVersion, TimeTracker timeTracker, boolean canRunWithoutTerminologyServer, ILoggingService loggingService, boolean THO) { + public ValidationEngineBuilder(String terminologyCachePath, String userAgent, String version, String txServer, String txLog, FhirPublication txVersion, boolean useEcosystem, TimeTracker timeTracker, boolean canRunWithoutTerminologyServer, ILoggingService loggingService, boolean THO) { this.terminologyCachePath = terminologyCachePath; this.userAgent = userAgent; this.version = version; @@ -361,15 +363,16 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP this.timeTracker = timeTracker; this.canRunWithoutTerminologyServer = canRunWithoutTerminologyServer; this.loggingService = loggingService; + this.useEcosystem = true; this.THO = THO; } - public ValidationEngineBuilder withTxServer(String txServer, String txLog, FhirPublication txVersion) { - return new ValidationEngineBuilder(terminologyCachePath, userAgent, version, txServer, txLog, txVersion, timeTracker, canRunWithoutTerminologyServer, loggingService, THO); + public ValidationEngineBuilder withTxServer(String txServer, String txLog, FhirPublication txVersion, boolean useEcosystem) { + return new ValidationEngineBuilder(terminologyCachePath, userAgent, version, txServer, txLog, txVersion, useEcosystem, timeTracker, canRunWithoutTerminologyServer, loggingService, THO); } public ValidationEngineBuilder withNoTerminologyServer() { - return new ValidationEngineBuilder(terminologyCachePath, userAgent, version, null, null, txVersion, timeTracker, true, loggingService, THO); + return new ValidationEngineBuilder(terminologyCachePath, userAgent, version, null, null, txVersion, useEcosystem, timeTracker, true, loggingService, THO); } public ValidationEngine fromNothing() throws IOException { @@ -393,7 +396,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP engine.getContext().setCanRunWithoutTerminology(canRunWithoutTerminologyServer); engine.getContext().setPackageTracker(engine); if (txServer != null) { - engine.setTerminologyServer(txServer, txLog, txVersion); + engine.setTerminologyServer(txServer, txLog, txVersion, useEcosystem); } engine.setVersion(version); engine.setIgLoader(new IgLoader(engine.getPcm(), engine.getContext(), engine.getVersion(), engine.isDebug())); @@ -514,11 +517,11 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP return ep; } - public String connectToTSServer(String url, String log, FhirPublication version) throws URISyntaxException, IOException, FHIRException { - return connectToTSServer(url, log, null, version); + public String connectToTSServer(String url, String log, FhirPublication version, boolean useEcosystem) throws URISyntaxException, IOException, FHIRException { + return connectToTSServer(url, log, null, version, useEcosystem); } - public String connectToTSServer(String url, String log, String txCachePath, FhirPublication version) throws URISyntaxException, IOException, FHIRException { + public String connectToTSServer(String url, String log, String txCachePath, FhirPublication version, boolean useEcosystem) throws URISyntaxException, IOException, FHIRException { context.setTlogging(false); if (url == null) { context.setCanRunWithoutTerminology(true); @@ -527,7 +530,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP } else { try { TerminologyClientFactory factory = new TerminologyClientFactory(version); - context.connectToTSServer(factory, url, context.getUserAgent(), log); + context.connectToTSServer(factory, url, context.getUserAgent(), log, useEcosystem); return "Connected to Terminology Server at "+url; } catch (Exception e) { if (context.isCanRunWithoutTerminology()) { @@ -1044,8 +1047,8 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP throw new FHIRException("Source/Target version not supported: " + version + " -> " + targetVer); } - public String setTerminologyServer(String src, String log, FhirPublication version) throws FHIRException, URISyntaxException, IOException { - return connectToTSServer(src, log, version); + public String setTerminologyServer(String src, String log, FhirPublication version, boolean useEcosystem) throws FHIRException, URISyntaxException, IOException { + return connectToTSServer(src, log, version, useEcosystem); } public ValidationEngine setMapLog(String mapLog) throws FileNotFoundException { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java index 8b987f826..30a012a15 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java @@ -88,6 +88,8 @@ public class CliContext { private String snomedCT = "900000000000207008"; @JsonProperty("targetVer") private String targetVer = null; + @JsonProperty("noEcosystem") + private boolean noEcosystem = false; @JsonProperty("extensions") private List extensions = new ArrayList(); @@ -245,6 +247,17 @@ public class CliContext { return this; } + @JsonProperty("txServer") + public boolean getNoEcosystem() { + return noEcosystem; + } + + @JsonProperty("txServer") + public CliContext setNoEcosystem(boolean noEcosystem) { + this.noEcosystem = noEcosystem; + return this; + } + @JsonProperty("doNative") public boolean isDoNative() { return doNative; diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java index da70975be..389fec6eb 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java @@ -493,6 +493,11 @@ public class ValidationService { System.out.println(" - " + validationEngine.getContext().countAllCaches() + " resources (" + timeTracker.milestone() + ")"); loadIgsAndExtensions(validationEngine, cliContext, timeTracker); + if (validationEngine.getContext().getTxCache() == null) { + System.out.println(" No Terminology Cache"); + } else { + System.out.println(" Terminology Cache at "+validationEngine.getContext().getTxCache().getFolder()); + } System.out.print(" Get set... "); validationEngine.setQuestionnaireMode(cliContext.getQuestionnaireMode()); validationEngine.setLevel(cliContext.getLevel()); @@ -546,7 +551,7 @@ public class ValidationService { igLoader.loadIg(validationEngine.getIgs(), validationEngine.getBinaries(), "hl7.fhir.uv.extensions", false); } System.out.print(" Terminology server " + cliContext.getTxServer()); - String txver = validationEngine.setTerminologyServer(cliContext.getTxServer(), cliContext.getTxLog(), ver); + String txver = validationEngine.setTerminologyServer(cliContext.getTxServer(), cliContext.getTxLog(), ver, !cliContext.getNoEcosystem()); System.out.println(" - Version " + txver + " (" + timeTracker.milestone() + ")"); validationEngine.setDebug(cliContext.isDoDebug()); validationEngine.getContext().setLogger(new SystemOutLoggingService(cliContext.isDoDebug())); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java index 46cd71f03..23136344d 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java @@ -340,8 +340,10 @@ public class Params { } else if (args[i].equals(TERMINOLOGY)) { if (i + 1 == args.length) throw new Error("Specified -tx without indicating terminology server"); - else + else { cliContext.setTxServer("n/a".equals(args[++i]) ? null : args[i]); + cliContext.setNoEcosystem(true); + } } else if (args[i].equals(TERMINOLOGY_LOG)) { if (i + 1 == args.length) throw new Error("Specified -txLog without indicating file"); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java index ef3065184..981ea9c17 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java @@ -256,6 +256,10 @@ public class ValueSetValidator extends BaseValidator { CodeSystem cs = null; if (!Utilities.noString(system)) { cs = context.fetchCodeSystem(system, version); + if (cs == null) { + // can we get it from a terminology server? + cs = context.findTxResource(CodeSystem.class, system, version); + } if (cs != null) { // if it's null, we can't analyse this switch (cs.getContent()) { case EXAMPLE: diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/utilities/TestUtilities.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/utilities/TestUtilities.java index 596854fad..31f8e3e6a 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/utilities/TestUtilities.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/utilities/TestUtilities.java @@ -22,7 +22,7 @@ public class TestUtilities { .withVersion(vString) .withUserAgent(TestConstants.USER_AGENT) .withTerminologyCachePath(getTerminologyCacheDirectory(vString)) - .withTxServer(txServer, txLog, version) + .withTxServer(txServer, txLog, version, false) .fromSource(src); TerminologyCache.setCacheErrors(true); @@ -59,7 +59,7 @@ public class TestUtilities { .withVersion(vString) .withUserAgent(TestConstants.USER_AGENT) .withTerminologyCachePath(getTerminologyCacheDirectory(vString)) - .withTxServer(txServer, TestConstants.TX_CACHE_LOG, version) + .withTxServer(txServer, TestConstants.TX_CACHE_LOG, version, false) .fromSource(src); TerminologyCache.setCacheErrors(true); } diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/cs-externals.json b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/cs-externals.json new file mode 100644 index 000000000..0192abcd3 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/cs-externals.json @@ -0,0 +1,8 @@ +{ + "#f3b2bd36-199b-4591-b4db-f49db0912b6|null" : null, + "#c1|null" : null, + "http://something/something|null" : null, + "http://hl7.org/fhir/CodeSystem/c1|null" : null, + "http://hl7.org/fhir/uv/sdc/CodeSystem/CSPHQ9|null" : null, + "#f3b2bd36-199b-4591-b4db-f49db0912b62|null" : null +} diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/cs-externals.json b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/cs-externals.json new file mode 100644 index 000000000..fdb076af7 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/cs-externals.json @@ -0,0 +1,6 @@ +{ + "#cs1|null" : null, + "#cs|null" : null, + "http://something|null" : null, + "http://snomed.info/sct|null" : null +}