Look up CodeSystem from terminology server + don't use tx-registry when manual terminology server is set

This commit is contained in:
Grahame Grieve 2024-04-23 08:34:25 +10:00
parent 46500069a4
commit 60bf358bfa
18 changed files with 345 additions and 32 deletions

View File

@ -1,7 +1,21 @@
## Validator Changes ## 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 ## Other code changes
* no changes * 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)

View File

@ -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.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; 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.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.utilities.TerminologyCache.SourcedValueSet;
import org.hl7.fhir.r5.terminologies.validation.VSCheckerException; import org.hl7.fhir.r5.terminologies.validation.VSCheckerException;
import org.hl7.fhir.r5.terminologies.validation.ValueSetValidator; import org.hl7.fhir.r5.terminologies.validation.ValueSetValidator;
@ -3234,6 +3235,28 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
cacheResource(svs.getVs()); cacheResource(svs.getVs());
return (T) 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 { } else {
throw new Error("Not supported"); throw new Error("Not supported");
} }

View File

@ -332,12 +332,12 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
loadBytes(name, stream); loadBytes(name, stream);
} }
public void connectToTSServer(ITerminologyClientFactory factory, ITerminologyClient client) { public void connectToTSServer(ITerminologyClientFactory factory, ITerminologyClient client, boolean useEcosystem) {
terminologyClientManager.setFactory(factory); terminologyClientManager.setFactory(factory);
if (txLog == null) { if (txLog == null) {
txLog = client.getLogger(); txLog = client.getLogger();
} }
TerminologyClientContext tcc = terminologyClientManager.setMasterClient(client); TerminologyClientContext tcc = terminologyClientManager.setMasterClient(client, useEcosystem);
txLog("Connect to "+client.getAddress()); txLog("Connect to "+client.getAddress());
try { try {
tcc.initialize(); 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 { try {
terminologyClientManager.setFactory(factory); terminologyClientManager.setFactory(factory);
if (log != null && (log.endsWith(".htm") || log.endsWith(".html"))) { 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) // txFactory.makeClient("Tx-Server", txServer, "fhir/publisher", null)
// terminologyClientManager.setLogger(txLog); // terminologyClientManager.setLogger(txLog);
// terminologyClientManager.setUserAgent(userAgent); // terminologyClientManager.setUserAgent(userAgent);
connectToTSServer(factory, client); connectToTSServer(factory, client, useEcosystem);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -71,9 +71,42 @@ import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.MarkDownProcessor; import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.StandardsStatus; import org.hl7.fhir.utilities.StandardsStatus;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
public class CodeSystemUtilities extends TerminologyUtilities { public class CodeSystemUtilities extends TerminologyUtilities {
public static class CodeSystemSorter implements Comparator<CodeSystem> {
@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 { public static class SystemReference {
private String link; private String link;
private String text; private String text;

View File

@ -16,19 +16,26 @@ import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache;
public class TerminologyClientContext { public class TerminologyClientContext {
public enum TerminologyClientContextUseType { public enum TerminologyClientContextUseType {
expand, validate, readVS expand, validate, readVS, readCS
} }
public class TerminologyClientContextUseCount { public class TerminologyClientContextUseCount {
private int expands; private int expands;
private int validates; private int validates;
private int readVS; private int readVS;
private int readCS;
public int getReadVS() { public int getReadVS() {
return readVS; return readVS;
} }
public void setReadVS(int readVS) { public void setReadVS(int readVS) {
this.readVS = readVS; this.readVS = readVS;
} }
public int getReadCS() {
return readCS;
}
public void setReadCS(int readCS) {
this.readCS = readCS;
}
public int getExpands() { public int getExpands() {
return expands; return expands;
} }
@ -94,6 +101,9 @@ public class TerminologyClientContext {
case readVS: case readVS:
uc.readVS++; uc.readVS++;
break; break;
case readCS:
uc.readCS++;
break;
case validate: case validate:
uc.validates++; uc.validates++;
break; break;

View File

@ -17,15 +17,18 @@ import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.CanonicalResourceManager; import org.hl7.fhir.r5.context.CanonicalResourceManager;
import org.hl7.fhir.r5.model.Bundle; import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; 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;
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r5.model.TerminologyCapabilities.TerminologyCapabilitiesCodeSystemComponent; import org.hl7.fhir.r5.model.TerminologyCapabilities.TerminologyCapabilitiesCodeSystemComponent;
import org.hl7.fhir.r5.model.TerminologyCapabilities; import org.hl7.fhir.r5.model.TerminologyCapabilities;
import org.hl7.fhir.r5.model.ValueSet; 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.ValueSetUtilities;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.TerminologyClientContextUseType; import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.TerminologyClientContextUseType;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager.ServerOptionList; 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;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedCodeSystem;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet; import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet;
import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
@ -100,6 +103,8 @@ public class TerminologyClientManager {
private String monitorServiceURL; private String monitorServiceURL;
private boolean useEcosystem;
public TerminologyClientManager(ITerminologyClientFactory factory, String cacheId) { public TerminologyClientManager(ITerminologyClientFactory factory, String cacheId) {
super(); super();
this.factory = factory; this.factory = factory;
@ -115,6 +120,7 @@ public class TerminologyClientManager {
serverList.addAll(other.serverList); serverList.addAll(other.serverList);
serverMap.putAll(other.serverMap); serverMap.putAll(other.serverMap);
resMap.putAll(other.resMap); resMap.putAll(other.resMap);
useEcosystem = other.useEcosystem;
monitorServiceURL = other.monitorServiceURL; monitorServiceURL = other.monitorServiceURL;
factory = other.factory; factory = other.factory;
usage = other.usage; usage = other.usage;
@ -238,7 +244,7 @@ public class TerminologyClientManager {
} }
private ServerOptionList decideWhichServer(String url) { private ServerOptionList decideWhichServer(String url) {
if (IGNORE_TX_REGISTRY) { if (IGNORE_TX_REGISTRY || !useEcosystem) {
return new ServerOptionList(getMasterClient().getAddress()); return new ServerOptionList(getMasterClient().getAddress());
} }
if (expParameters != null) { 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); TerminologyClientContext details = new TerminologyClientContext(client, cacheId, true);
details.setTxCache(cache); details.setTxCache(cache);
serverList.clear(); serverList.clear();
@ -419,7 +426,7 @@ public class TerminologyClientManager {
} }
public SourcedValueSet findValueSetOnServer(String canonical) { public SourcedValueSet findValueSetOnServer(String canonical) {
if (IGNORE_TX_REGISTRY || getMasterClient() == null) { if (IGNORE_TX_REGISTRY || getMasterClient() == null || !useEcosystem) {
return null; return null;
} }
String request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&valueSet="+Utilities.URLEncode(canonical)); 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<CodeSystem> 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 { public boolean supportsSystem(String system) throws IOException {
for (TerminologyClientContext client : serverList) { for (TerminologyClientContext client : serverList) {
if (client.supportsSystem(system)) { if (client.supportsSystem(system)) {

View File

@ -83,6 +83,42 @@ import com.google.gson.JsonPrimitive;
*/ */
public class TerminologyCache { 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 { public static class SourcedValueSet {
private String server; private String server;
private ValueSet vs; private ValueSet vs;
@ -253,6 +289,7 @@ public class TerminologyCache {
private Map<String, TerminologyCapabilities> terminologyCapabilitiesCache = new HashMap<>(); private Map<String, TerminologyCapabilities> terminologyCapabilitiesCache = new HashMap<>();
private Map<String, NamedCache> caches = new HashMap<String, NamedCache>(); private Map<String, NamedCache> caches = new HashMap<String, NamedCache>();
private Map<String, SourcedValueSetEntry> vsCache = new HashMap<>(); private Map<String, SourcedValueSetEntry> vsCache = new HashMap<>();
private Map<String, SourcedCodeSystemEntry> csCache = new HashMap<>();
private Map<String, String> serverMap = new HashMap<>(); private Map<String, String> serverMap = new HashMap<>();
@Getter @Setter private static boolean noCaching; @Getter @Setter private static boolean noCaching;
@ -320,12 +357,14 @@ public class TerminologyCache {
// not useable after this is called // not useable after this is called
caches.clear(); caches.clear();
vsCache.clear(); vsCache.clear();
csCache.clear();
} }
private void clear() throws IOException { private void clear() throws IOException {
Utilities.clearDirectory(folder); Utilities.clearDirectory(folder);
caches.clear(); caches.clear();
vsCache.clear(); vsCache.clear();
csCache.clear();
} }
public boolean hasCapabilityStatement(String address) { public boolean hasCapabilityStatement(String address) {
@ -872,6 +911,22 @@ public class TerminologyCache {
} catch (Exception e) { } catch (Exception e) {
System.out.println("Error loading vs external cache: "+e.getMessage()); 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) { private String loadJS(JsonElement e) {
@ -978,6 +1033,10 @@ public class TerminologyCache {
return vsCache.containsKey(canonical); return vsCache.containsKey(canonical);
} }
public boolean hasCodeSystem(String canonical) {
return csCache.containsKey(canonical);
}
public SourcedValueSet getValueSet(String canonical) { public SourcedValueSet getValueSet(String canonical) {
SourcedValueSetEntry sp = vsCache.get(canonical); SourcedValueSetEntry sp = vsCache.get(canonical);
if (sp == null) { 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) { public void cacheValueSet(String canonical, SourcedValueSet svs) {
if (canonical == null) { if (canonical == null) {
return; 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) { public CacheToken generateSubsumesToken(ValidationOptions options, Coding parent, Coding child, Parameters expParameters) {
try { try {
CacheToken ct = new CacheToken(); CacheToken ct = new CacheToken();

View File

@ -97,7 +97,7 @@ public class SimpleWorkerContextTests {
Mockito.doReturn(DUMMY_URL).when(terminologyClient).getAddress(); Mockito.doReturn(DUMMY_URL).when(terminologyClient).getAddress();
context.initTxCache(terminologyCache); context.initTxCache(terminologyCache);
context.expParameters = expParameters; context.expParameters = expParameters;
context.terminologyClientManager.setMasterClient(terminologyClient); context.terminologyClientManager.setMasterClient(terminologyClient, false);
context.txLog = txLog; context.txLog = txLog;
} }
@ -437,7 +437,7 @@ public class SimpleWorkerContextTests {
Mockito.doReturn(terminologyCapabilities).when(terminologyCache).getTerminologyCapabilities(address); 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(terminologyCache).getTerminologyCapabilities(address);
Mockito.verify(terminologyClient).getCapabilitiesStatementQuick(); Mockito.verify(terminologyClient).getCapabilitiesStatementQuick();
@ -455,7 +455,7 @@ public class SimpleWorkerContextTests {
Mockito.doReturn(terminologyCapabilities).when(terminologyClient).getTerminologyCapabilities(); Mockito.doReturn(terminologyCapabilities).when(terminologyClient).getTerminologyCapabilities();
Mockito.doReturn(capabilitiesStatement).when(terminologyClient).getCapabilitiesStatementQuick(); 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)).getTerminologyCapabilities(address);
Mockito.verify(terminologyCache, times(0)).getCapabilityStatement(address); Mockito.verify(terminologyCache, times(0)).getCapabilityStatement(address);

View File

@ -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_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}'') 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 = 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_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_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}'' VALUESET_BAD_FILTER_VALUE_DATETIME = The value for a filter based on property ''{0}'' must be a valid date(/time), not ''{1}''

View File

@ -161,7 +161,7 @@ public class NativeHostServices {
* @throws Exception * @throws Exception
*/ */
public void connectToTxSvc(String txServer, String log) 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 * @throws Exception
*/ */
public void connectToTxSvc(String txServer, String log, String txCache) 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);
} }
/** /**

View File

@ -325,6 +325,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
private final String txServer; private final String txServer;
private final String txLog; private final String txLog;
private final FhirPublication txVersion; private final FhirPublication txVersion;
private final boolean useEcosystem;
@With @With
private final TimeTracker timeTracker; private final TimeTracker timeTracker;
@ -348,10 +349,11 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
txVersion = null; txVersion = null;
timeTracker = null; timeTracker = null;
canRunWithoutTerminologyServer = false; canRunWithoutTerminologyServer = false;
useEcosystem = true;
loggingService = new SystemOutLoggingService(); 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.terminologyCachePath = terminologyCachePath;
this.userAgent = userAgent; this.userAgent = userAgent;
this.version = version; this.version = version;
@ -361,15 +363,16 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
this.timeTracker = timeTracker; this.timeTracker = timeTracker;
this.canRunWithoutTerminologyServer = canRunWithoutTerminologyServer; this.canRunWithoutTerminologyServer = canRunWithoutTerminologyServer;
this.loggingService = loggingService; this.loggingService = loggingService;
this.useEcosystem = true;
this.THO = THO; this.THO = THO;
} }
public ValidationEngineBuilder withTxServer(String txServer, String txLog, FhirPublication txVersion) { public ValidationEngineBuilder withTxServer(String txServer, String txLog, FhirPublication txVersion, boolean useEcosystem) {
return new ValidationEngineBuilder(terminologyCachePath, userAgent, version, txServer, txLog, txVersion, timeTracker, canRunWithoutTerminologyServer, loggingService, THO); return new ValidationEngineBuilder(terminologyCachePath, userAgent, version, txServer, txLog, txVersion, useEcosystem, timeTracker, canRunWithoutTerminologyServer, loggingService, THO);
} }
public ValidationEngineBuilder withNoTerminologyServer() { 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 { public ValidationEngine fromNothing() throws IOException {
@ -393,7 +396,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
engine.getContext().setCanRunWithoutTerminology(canRunWithoutTerminologyServer); engine.getContext().setCanRunWithoutTerminology(canRunWithoutTerminologyServer);
engine.getContext().setPackageTracker(engine); engine.getContext().setPackageTracker(engine);
if (txServer != null) { if (txServer != null) {
engine.setTerminologyServer(txServer, txLog, txVersion); engine.setTerminologyServer(txServer, txLog, txVersion, useEcosystem);
} }
engine.setVersion(version); engine.setVersion(version);
engine.setIgLoader(new IgLoader(engine.getPcm(), engine.getContext(), engine.getVersion(), engine.isDebug())); engine.setIgLoader(new IgLoader(engine.getPcm(), engine.getContext(), engine.getVersion(), engine.isDebug()));
@ -514,11 +517,11 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
return ep; return ep;
} }
public String connectToTSServer(String url, String log, FhirPublication version) throws URISyntaxException, IOException, FHIRException { public String connectToTSServer(String url, String log, FhirPublication version, boolean useEcosystem) throws URISyntaxException, IOException, FHIRException {
return connectToTSServer(url, log, null, version); 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); context.setTlogging(false);
if (url == null) { if (url == null) {
context.setCanRunWithoutTerminology(true); context.setCanRunWithoutTerminology(true);
@ -527,7 +530,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
} else { } else {
try { try {
TerminologyClientFactory factory = new TerminologyClientFactory(version); 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; return "Connected to Terminology Server at "+url;
} catch (Exception e) { } catch (Exception e) {
if (context.isCanRunWithoutTerminology()) { if (context.isCanRunWithoutTerminology()) {
@ -1044,8 +1047,8 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
throw new FHIRException("Source/Target version not supported: " + version + " -> " + targetVer); throw new FHIRException("Source/Target version not supported: " + version + " -> " + targetVer);
} }
public String setTerminologyServer(String src, String log, FhirPublication version) throws FHIRException, URISyntaxException, IOException { public String setTerminologyServer(String src, String log, FhirPublication version, boolean useEcosystem) throws FHIRException, URISyntaxException, IOException {
return connectToTSServer(src, log, version); return connectToTSServer(src, log, version, useEcosystem);
} }
public ValidationEngine setMapLog(String mapLog) throws FileNotFoundException { public ValidationEngine setMapLog(String mapLog) throws FileNotFoundException {

View File

@ -88,6 +88,8 @@ public class CliContext {
private String snomedCT = "900000000000207008"; private String snomedCT = "900000000000207008";
@JsonProperty("targetVer") @JsonProperty("targetVer")
private String targetVer = null; private String targetVer = null;
@JsonProperty("noEcosystem")
private boolean noEcosystem = false;
@JsonProperty("extensions") @JsonProperty("extensions")
private List<String> extensions = new ArrayList<String>(); private List<String> extensions = new ArrayList<String>();
@ -245,6 +247,17 @@ public class CliContext {
return this; return this;
} }
@JsonProperty("txServer")
public boolean getNoEcosystem() {
return noEcosystem;
}
@JsonProperty("txServer")
public CliContext setNoEcosystem(boolean noEcosystem) {
this.noEcosystem = noEcosystem;
return this;
}
@JsonProperty("doNative") @JsonProperty("doNative")
public boolean isDoNative() { public boolean isDoNative() {
return doNative; return doNative;

View File

@ -493,6 +493,11 @@ public class ValidationService {
System.out.println(" - " + validationEngine.getContext().countAllCaches() + " resources (" + timeTracker.milestone() + ")"); System.out.println(" - " + validationEngine.getContext().countAllCaches() + " resources (" + timeTracker.milestone() + ")");
loadIgsAndExtensions(validationEngine, cliContext, timeTracker); 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... "); System.out.print(" Get set... ");
validationEngine.setQuestionnaireMode(cliContext.getQuestionnaireMode()); validationEngine.setQuestionnaireMode(cliContext.getQuestionnaireMode());
validationEngine.setLevel(cliContext.getLevel()); validationEngine.setLevel(cliContext.getLevel());
@ -546,7 +551,7 @@ public class ValidationService {
igLoader.loadIg(validationEngine.getIgs(), validationEngine.getBinaries(), "hl7.fhir.uv.extensions", false); igLoader.loadIg(validationEngine.getIgs(), validationEngine.getBinaries(), "hl7.fhir.uv.extensions", false);
} }
System.out.print(" Terminology server " + cliContext.getTxServer()); 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() + ")"); System.out.println(" - Version " + txver + " (" + timeTracker.milestone() + ")");
validationEngine.setDebug(cliContext.isDoDebug()); validationEngine.setDebug(cliContext.isDoDebug());
validationEngine.getContext().setLogger(new SystemOutLoggingService(cliContext.isDoDebug())); validationEngine.getContext().setLogger(new SystemOutLoggingService(cliContext.isDoDebug()));

View File

@ -340,8 +340,10 @@ public class Params {
} else if (args[i].equals(TERMINOLOGY)) { } else if (args[i].equals(TERMINOLOGY)) {
if (i + 1 == args.length) if (i + 1 == args.length)
throw new Error("Specified -tx without indicating terminology server"); throw new Error("Specified -tx without indicating terminology server");
else else {
cliContext.setTxServer("n/a".equals(args[++i]) ? null : args[i]); cliContext.setTxServer("n/a".equals(args[++i]) ? null : args[i]);
cliContext.setNoEcosystem(true);
}
} else if (args[i].equals(TERMINOLOGY_LOG)) { } else if (args[i].equals(TERMINOLOGY_LOG)) {
if (i + 1 == args.length) if (i + 1 == args.length)
throw new Error("Specified -txLog without indicating file"); throw new Error("Specified -txLog without indicating file");

View File

@ -256,6 +256,10 @@ public class ValueSetValidator extends BaseValidator {
CodeSystem cs = null; CodeSystem cs = null;
if (!Utilities.noString(system)) { if (!Utilities.noString(system)) {
cs = context.fetchCodeSystem(system, version); 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 if (cs != null) { // if it's null, we can't analyse this
switch (cs.getContent()) { switch (cs.getContent()) {
case EXAMPLE: case EXAMPLE:

View File

@ -22,7 +22,7 @@ public class TestUtilities {
.withVersion(vString) .withVersion(vString)
.withUserAgent(TestConstants.USER_AGENT) .withUserAgent(TestConstants.USER_AGENT)
.withTerminologyCachePath(getTerminologyCacheDirectory(vString)) .withTerminologyCachePath(getTerminologyCacheDirectory(vString))
.withTxServer(txServer, txLog, version) .withTxServer(txServer, txLog, version, false)
.fromSource(src); .fromSource(src);
TerminologyCache.setCacheErrors(true); TerminologyCache.setCacheErrors(true);
@ -59,7 +59,7 @@ public class TestUtilities {
.withVersion(vString) .withVersion(vString)
.withUserAgent(TestConstants.USER_AGENT) .withUserAgent(TestConstants.USER_AGENT)
.withTerminologyCachePath(getTerminologyCacheDirectory(vString)) .withTerminologyCachePath(getTerminologyCacheDirectory(vString))
.withTxServer(txServer, TestConstants.TX_CACHE_LOG, version) .withTxServer(txServer, TestConstants.TX_CACHE_LOG, version, false)
.fromSource(src); .fromSource(src);
TerminologyCache.setCacheErrors(true); TerminologyCache.setCacheErrors(true);
} }

View File

@ -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
}

View File

@ -0,0 +1,6 @@
{
"#cs1|null" : null,
"#cs|null" : null,
"http://something|null" : null,
"http://snomed.info/sct|null" : null
}