rework terminology cache to manage multiple terminology servers

This commit is contained in:
Grahame Grieve 2024-01-17 20:44:37 +11:00
parent 63c62bb80c
commit 8613cd3944
21 changed files with 464 additions and 250 deletions

View File

@ -6,6 +6,7 @@ import org.hl7.fhir.r5.terminologies.client.ITerminologyClient;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager.ITerminologyClientFactory;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientR5;
import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.ToolingClientLogger;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
@ -23,27 +24,28 @@ public class TerminologyClientFactory implements ITerminologyClientFactory {
this.v = version;
}
public ITerminologyClient makeClient(String id, String url, String userAgent) throws URISyntaxException {
@Override
public ITerminologyClient makeClient(String id, String url, String userAgent, ToolingClientLogger logger) throws URISyntaxException {
if (v == null)
return new TerminologyClientR5(id, checkEndsWith("/r4", url), userAgent);
return new TerminologyClientR5(id, checkEndsWith("/r4", url), userAgent).setLogger(logger);
v = VersionUtilities.getMajMin(v);
if (VersionUtilities.isR2Ver(v)) {
return new TerminologyClientR2(id, checkEndsWith("/r2", url), userAgent);
return new TerminologyClientR2(id, checkEndsWith("/r2", url), userAgent).setLogger(logger);
}
if (VersionUtilities.isR2BVer(v)) {
return new TerminologyClientR3(id, checkEndsWith("/r3", url), userAgent); // r3 is the least worst match
return new TerminologyClientR3(id, checkEndsWith("/r3", url), userAgent).setLogger(logger); // r3 is the least worst match
}
if (VersionUtilities.isR3Ver(v)) {
return new TerminologyClientR3(id, checkEndsWith("/r3", url), userAgent); // r3 is the least worst match
return new TerminologyClientR3(id, checkEndsWith("/r3", url), userAgent).setLogger(logger); // r3 is the least worst match
}
if (VersionUtilities.isR4Ver(v)) {
return new TerminologyClientR4(id, checkEndsWith("/r4", url), userAgent);
return new TerminologyClientR4(id, checkEndsWith("/r4", url), userAgent).setLogger(logger);
}
if (VersionUtilities.isR4BVer(v)) {
return new TerminologyClientR4(id, checkEndsWith("/r4", url), userAgent);
return new TerminologyClientR4(id, checkEndsWith("/r4", url), userAgent).setLogger(logger);
}
if (VersionUtilities.isR5Plus(v)) {
return new TerminologyClientR5(id, checkEndsWith("/r4", url), userAgent); // r4 for now, since the terminology is currently the same
return new TerminologyClientR5(id, checkEndsWith("/r4", url), userAgent).setLogger(logger); // r4 for now, since the terminology is currently the same
}
throw new Error("The version " + v + " is not currently supported");
}

View File

@ -131,6 +131,11 @@ public class TerminologyClientR2 implements ITerminologyClient {
return this;
}
@Override
public ToolingClientLogger getLogger() {
return client.getLogger();
}
@Override
public ITerminologyClient setLogger(ToolingClientLogger txLog) {
client.setLogger(txLog);

View File

@ -127,6 +127,11 @@ public class TerminologyClientR3 implements ITerminologyClient {
client.setTimeoutFactor(i);
return this;
}
@Override
public ToolingClientLogger getLogger() {
return client.getLogger();
}
@Override
public ITerminologyClient setLogger(ToolingClientLogger txLog) {

View File

@ -138,6 +138,11 @@ public class TerminologyClientR4 implements ITerminologyClient {
return this;
}
@Override
public ToolingClientLogger getLogger() {
return client.getLogger();
}
@Override
public ITerminologyClient setLogger(ToolingClientLogger txLog) {
client.setLogger(txLog);

View File

@ -53,14 +53,14 @@ public class TerminologyClientFactoryTest {
@ParameterizedTest
@MethodSource("data")
public void testMakeClient(String url, FhirPublication fhirPublication, String expectedAddress) throws URISyntaxException {
ITerminologyClient terminologyClient = new TerminologyClientFactory(fhirPublication).makeClient("id", url, "dummyUserAgent");
ITerminologyClient terminologyClient = new TerminologyClientFactory(fhirPublication).makeClient("id", url, "dummyUserAgent", null);
assertEquals(expectedAddress, terminologyClient.getAddress());
}
@Test
public void testMakeClientDstu1Fails() throws URISyntaxException {
assertThrows(Error.class, () -> {
ITerminologyClient terminologyClient = new TerminologyClientFactory(FhirPublication.DSTU1).makeClient("id", "urldoesnotmatter", "dummyUserAgent");
ITerminologyClient terminologyClient = new TerminologyClientFactory(FhirPublication.DSTU1).makeClient("id", "urldoesnotmatter", "dummyUserAgent", null);
}
);
}
@ -68,7 +68,7 @@ public class TerminologyClientFactoryTest {
@Test
public void testMakeClientNullFails() throws URISyntaxException {
assertThrows(Error.class, () -> {
ITerminologyClient terminologyClient = new TerminologyClientFactory(FhirPublication.NULL).makeClient("id", "urldoesnotmatter", "dummyUserAgent");
ITerminologyClient terminologyClient = new TerminologyClientFactory(FhirPublication.NULL).makeClient("id", "urldoesnotmatter", "dummyUserAgent", null);
}
);
}

View File

@ -48,6 +48,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
@ -137,6 +138,7 @@ import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.client.EFhirClientException;
import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier;
import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.utilities.ToolingClientLogger;
import org.hl7.fhir.utilities.TranslationServices;
@ -232,7 +234,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
private Object lock = new Object(); // used as a lock for the data that follows
protected String version; // although the internal resources are all R5, the version of FHIR they describe may not be
protected TerminologyClientManager terminologyClientManager = new TerminologyClientManager(null);
protected final TerminologyClientManager terminologyClientManager = new TerminologyClientManager(null, UUID.randomUUID().toString());
private boolean minimalMemory = false;
private Map<String, Map<String, ResourceProxy>> allResourcesById = new HashMap<String, Map<String, ResourceProxy>>();
@ -270,7 +272,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
private final Set<String> codeSystemsUsed = new HashSet<>();
protected ToolingClientLogger txLog;
private boolean canRunWithoutTerminology;
protected boolean canRunWithoutTerminology;
protected boolean noTerminologyServer;
private int expandCodesLimit = 1000;
protected ILoggingService logger = new SystemOutLoggingService();
@ -279,7 +281,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
private Map<String, PackageInformation> packages = new HashMap<>();
@Getter
protected TerminologyCache txCache;
protected TerminologyCache txCache = new TerminologyCache(this, null);
protected TimeTracker clock;
private boolean tlogging = true;
private IWorkerContextManager.ICanonicalResourceLocator locator;
@ -775,12 +777,11 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
if (noTerminologyServer) {
return false;
}
if (terminologyClientManager.getTxCapabilities() == null) {
if (terminologyClientManager != null) {
try {
logger.logMessage("Terminology server: Check for supported code systems for "+system);
final TerminologyCapabilities capabilityStatement = txCache.hasTerminologyCapabilities() ? txCache.getTerminologyCapabilities() : terminologyClientManager.getMasterClient().getTerminologyCapabilities();
txCache.cacheTerminologyCapabilities(capabilityStatement);
setTxCaps(capabilityStatement);
if (terminologyClientManager.supportsSystem(system)) {
supportedCodeSystems.add(system);
}
} catch (Exception e) {
if (canRunWithoutTerminology) {
noTerminologyServer = true;
@ -1374,15 +1375,15 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
}
protected ValueSetExpander constructValueSetExpanderSimple(ValidationOptions options) {
return new ValueSetExpander(this, new TerminologyOperationContext(this, options));
return new ValueSetExpander(this, new TerminologyOperationContext(this, options)).setDebug(logger.isDebugLogging());
}
protected ValueSetValidator constructValueSetCheckerSimple(ValidationOptions options, ValueSet vs, ValidationContextCarrier ctxt) {
return new ValueSetValidator(this, new TerminologyOperationContext(this, options), options, vs, ctxt, expParameters, terminologyClientManager.getTxCapabilities());
return new ValueSetValidator(this, new TerminologyOperationContext(this, options), options, vs, ctxt, expParameters, terminologyClientManager);
}
protected ValueSetValidator constructValueSetCheckerSimple( ValidationOptions options, ValueSet vs) {
return new ValueSetValidator(this, new TerminologyOperationContext(this, options), options, vs, expParameters, terminologyClientManager.getTxCapabilities());
return new ValueSetValidator(this, new TerminologyOperationContext(this, options), options, vs, expParameters, terminologyClientManager);
}
protected Parameters constructParameters(TerminologyClientContext tcd, ValueSet vs, boolean hierarchical) {
@ -1646,8 +1647,11 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
protected void addServerValidationParameters(TerminologyClientContext terminologyClientContext, ValueSet vs, Parameters pin, ValidationOptions options) {
boolean cache = false;
if (vs != null) {
if (terminologyClientManager.isTxCaching() && terminologyClientManager.getCacheId() != null && vs.getUrl() != null && terminologyClientContext.getCached().contains(vs.getUrl()+"|"+ vs.getVersion())) {
pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()+(vs.hasVersion() ? "|"+ vs.getVersion() : "")));
if (terminologyClientContext != null && terminologyClientContext.isTxCaching() && terminologyClientContext.getCacheId() != null && vs.getUrl() != null && terminologyClientContext.getCached().contains(vs.getUrl()+"|"+ vs.getVersion())) {
pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()));
if (vs.hasVersion()) {
pin.addParameter().setName("valueSetVersion").setValue(new StringType(vs.getVersion()));
}
} else if (options.getVsAsUrl()){
pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()));
} else {
@ -1706,7 +1710,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
private boolean checkAddToParams(TerminologyClientContext tc, Parameters pin, CanonicalResource cr) {
boolean cache = false;
boolean addToParams = false;
if (terminologyClientManager.usingCache()) {
if (tc.usingCache()) {
if (!tc.alreadyCached(cr)) {
tc.addToCache(cr);
if (logger.isDebugLogging()) {
@ -1857,11 +1861,15 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
return "item";
}
protected void initTS(String cachePath) throws IOException {
if (cachePath != null && !new File(cachePath).exists()) {
Utilities.createDirectory(cachePath);
public void initTxCache(String cachePath) throws FileNotFoundException, FHIRException, IOException {
if (cachePath != null) {
txCache = new TerminologyCache(lock, cachePath);
initTxCache(txCache);
}
txCache = new TerminologyCache(lock, cachePath);
}
public void initTxCache(TerminologyCache cache) {
txCache = cache;
terminologyClientManager.setCache(txCache);
}
@ -1869,10 +1877,6 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
txCache.removeCS(url);
}
public void clearTS() {
txCache.clear();
}
public boolean isCanRunWithoutTerminology() {
return canRunWithoutTerminology;
}
@ -2994,33 +2998,10 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
return terminologyClientManager.getCacheId();
}
public void setCacheId(String cacheId) {
terminologyClientManager.setCacheId(cacheId);
}
public TerminologyCapabilities getTxCaps() {
return terminologyClientManager.getTxCapabilities();
}
public void setTxCaps(TerminologyCapabilities txCaps) {
this.terminologyClientManager.setTxCapabilities(txCaps);
if (txCaps != null) {
for (TerminologyCapabilitiesExpansionParameterComponent t : terminologyClientManager.getTxCapabilities().getExpansion().getParameter()) {
if ("cache-id".equals(t.getName())) {
terminologyClientManager.setTxCaching(true);
}
}
for (TerminologyCapabilitiesCodeSystemComponent tccs : terminologyClientManager.getTxCapabilities().getCodeSystem()) {
supportedCodeSystems.add(tccs.getUri());
}
}
}
public TimeTracker clock() {
return clock;
}
public int countAllCaches() {
return codeSystems.size() + valueSets.size() + maps.size() + transforms.size() + structures.size() + measures.size() + libraries.size() +
guides.size() + capstmts.size() + searchParameters.size() + questionnaires.size() + operations.size() + plans.size() +
@ -3171,7 +3152,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
binaries.clear();
validationCache.clear();
txCache.clear();
txCache.unload();
}
private <T extends Resource> T doFindTxResource(Class<T> class_, String canonical) {
@ -3180,8 +3161,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
SourcedValueSet svs = null;
if (txCache.hasValueSet(canonical)) {
svs = txCache.getValueSet(canonical);
}
if (svs == null) {
} else {
svs = terminologyClientManager.findValueSetOnServer(canonical);
txCache.cacheValueSet(canonical, svs);
}
@ -3205,6 +3185,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
}
public <T extends Resource> T findTxResource(Class<T> class_, String canonical, Resource sourceOfReference) {
if (canonical == null) {
return null;
}
T result = fetchResource(class_, canonical, sourceOfReference);
if (result == null) {
result = doFindTxResource(class_, canonical);
@ -3213,6 +3196,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
}
public <T extends Resource> T findTxResource(Class<T> class_, String canonical) {
if (canonical == null) {
return null;
}
T result = fetchResource(class_, canonical);
if (result == null) {
result = doFindTxResource(class_, canonical);
@ -3221,6 +3207,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
}
public <T extends Resource> T findTxResource(Class<T> class_, String canonical, String version) {
if (canonical == null) {
return null;
}
T result = fetchResource(class_, canonical, version);
if (result == null) {
result = doFindTxResource(class_, canonical+"|"+version);

View File

@ -41,6 +41,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@ -51,6 +52,7 @@ import org.apache.commons.io.IOUtils;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.CanonicalResourceManager.CanonicalResourceProxy;
import org.hl7.fhir.r5.context.ILoggingService.LogCategory;
import org.hl7.fhir.r5.formats.IParser;
@ -64,6 +66,7 @@ import org.hl7.fhir.r5.model.StructureMap.StructureMapModelMode;
import org.hl7.fhir.r5.model.StructureMap.StructureMapStructureComponent;
import org.hl7.fhir.r5.terminologies.JurisdictionUtilities;
import org.hl7.fhir.r5.terminologies.client.ITerminologyClient;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager.ITerminologyClientFactory;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientR5;
@ -237,7 +240,7 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
}
private SimpleWorkerContext build(SimpleWorkerContext context) throws IOException {
context.initTS(terminologyCachePath);
context.initTxCache(terminologyCachePath);
context.setUserAgent(userAgent);
context.setLogger(loggingService);
context.cacheResource(new org.hl7.fhir.r5.formats.JsonParser().parse(MagicResources.spdxCodesAsData()));
@ -247,7 +250,7 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
public SimpleWorkerContext fromPackage(NpmPackage pi) throws IOException, FHIRException {
SimpleWorkerContext context = getSimpleWorkerContextInstance();
context.setAllowLoadingDuplicates(allowLoadingDuplicates);
context.terminologyClientManager = new TerminologyClientManager(TerminologyClientR5.factory());
context.terminologyClientManager.setFactory(TerminologyClientR5.factory());
context.loadFromPackage(pi, null);
return build(context);
}
@ -328,30 +331,48 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
loadBytes(name, stream);
}
public String connectToTSServer(ITerminologyClientFactory factory, ITerminologyClient client, String log) {
public void connectToTSServer(ITerminologyClientFactory factory, ITerminologyClient client) {
terminologyClientManager.setFactory(factory);
if (txLog == null) {
txLog = client.getLogger();
}
TerminologyClientContext tcc = terminologyClientManager.setMasterClient(client);
txLog("Connect to "+client.getAddress());
try {
tcc.initialize();
} catch (Exception e) {
if (canRunWithoutTerminology) {
noTerminologyServer = true;
logger.logMessage("==============!! Running without terminology server !! ==============");
if (terminologyClientManager.getMasterClient() != null) {
logger.logMessage("txServer = "+ terminologyClientManager.getMasterClient().getId());
logger.logMessage("Error = "+e.getMessage()+"");
}
logger.logMessage("=====================================================================");
} else {
e.printStackTrace();
throw new TerminologyServiceException(e);
}
}
}
public void connectToTSServer(ITerminologyClientFactory factory, String address, String software, String log) {
try {
txLog("Connect to "+client.getAddress());
terminologyClientManager.setFactory(factory);
terminologyClientManager.setMasterClient(client);
if (log != null && (log.endsWith(".htm") || log.endsWith(".html"))) {
txLog = new HTMLClientLogger(log);
} else {
txLog = new TextClientLogger(log);
}
terminologyClientManager.setLogger(txLog);
terminologyClientManager.setUserAgent(userAgent);
final CapabilityStatement capabilitiesStatementQuick = txCache.hasCapabilityStatement() ? txCache.getCapabilityStatement() : terminologyClientManager.getMasterClient().getCapabilitiesStatementQuick();
txCache.cacheCapabilityStatement(capabilitiesStatementQuick);
final TerminologyCapabilities capabilityStatement = txCache.hasTerminologyCapabilities() ? txCache.getTerminologyCapabilities() : terminologyClientManager.getMasterClient().getTerminologyCapabilities();
txCache.cacheTerminologyCapabilities(capabilityStatement);
setTxCaps(capabilityStatement);
return capabilitiesStatementQuick.getSoftware().getVersion();
}
ITerminologyClient client = factory.makeClient("tx-server", address, software, txLog);
// txFactory.makeClient("Tx-Server", txServer, "fhir/publisher", null)
// terminologyClientManager.setLogger(txLog);
// terminologyClientManager.setUserAgent(userAgent);
connectToTSServer(factory, client);
} catch (Exception e) {
e.printStackTrace();
throw new FHIRException(formatMessage(canNoTS ? I18nConstants.UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER_USE_PARAMETER_TX_NA_TUN_RUN_WITHOUT_USING_TERMINOLOGY_SERVICES_TO_VALIDATE_LOINC_SNOMED_ICDX_ETC_ERROR__ : I18nConstants.UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER, e.getMessage(), client.getAddress()), e);
throw new FHIRException(formatMessage(canNoTS ? I18nConstants.UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER_USE_PARAMETER_TX_NA_TUN_RUN_WITHOUT_USING_TERMINOLOGY_SERVICES_TO_VALIDATE_LOINC_SNOMED_ICDX_ETC_ERROR__ : I18nConstants.UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER, e.getMessage(), address), e);
}
}

View File

@ -55,6 +55,7 @@ public interface ITerminologyClient {
Parameters validateCS(Parameters pin) throws FHIRException;
Parameters validateVS(Parameters pin) throws FHIRException;
ITerminologyClient setTimeoutFactor(int i) throws FHIRException;
ToolingClientLogger getLogger();
ITerminologyClient setLogger(ToolingClientLogger txLog) throws FHIRException;
int getRetryCount() throws FHIRException;
ITerminologyClient setRetryCount(int retryCount) throws FHIRException;

View File

@ -1,13 +1,18 @@
package org.hl7.fhir.r5.terminologies.client;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CapabilityStatement;
import org.hl7.fhir.r5.model.TerminologyCapabilities;
import org.hl7.fhir.r5.model.TerminologyCapabilities.TerminologyCapabilitiesCodeSystemComponent;
import org.hl7.fhir.r5.model.TerminologyCapabilities.TerminologyCapabilitiesExpansionParameterComponent;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.TerminologyClientContextUseCount;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache;
public class TerminologyClientContext {
public enum TerminologyClientContextUseType {
@ -40,14 +45,21 @@ public class TerminologyClientContext {
}
private ITerminologyClient client;
Map<String, TerminologyClientContextUseCount> useCounts = new HashMap<>();
private boolean initialised = false;
private CapabilityStatement capabilitiesStatementQuick;
private TerminologyCapabilities txcaps;
private TerminologyCache txCache;
private Map<String, TerminologyClientContextUseCount> useCounts = new HashMap<>();
private boolean isTxCaching;
private final Set<String> cached = new HashSet<>();
private boolean master;
private String cacheId;
protected TerminologyClientContext(ITerminologyClient client, boolean master) {
protected TerminologyClientContext(ITerminologyClient client, String cacheId, boolean master) {
super();
this.client = client;
this.cacheId = cacheId;
this.master = master;
}
@ -84,11 +96,11 @@ public class TerminologyClientContext {
}
}
public TerminologyCapabilities getTxcaps() {
public TerminologyCapabilities getTxCapabilities() {
return txcaps;
}
public void setTxcaps(TerminologyCapabilities txcaps) {
public void setTxCapabilities(TerminologyCapabilities txcaps) {
this.txcaps = txcaps;
}
@ -112,4 +124,65 @@ public class TerminologyClientContext {
return getClient().getUseCount();
}
public boolean isTxCaching() {
return isTxCaching;
}
public void setTxCaching(boolean isTxCaching) {
this.isTxCaching = isTxCaching;
}
public boolean usingCache() {
return isTxCaching && cacheId != null;
}
public String getCacheId() {
return cacheId;
}
public TerminologyCache getTxCache() {
return txCache;
}
public void setTxCache(TerminologyCache txCache) {
this.txCache = txCache;
}
public void initialize() throws IOException {
if (!initialised) {
// we don't cache the quick CS - we want to know that the server is with us.
capabilitiesStatementQuick = client.getCapabilitiesStatementQuick();
if (txCache != null && txCache.hasTerminologyCapabilities(getAddress())) {
txcaps = txCache.getTerminologyCapabilities(getAddress());
if (txcaps.getSoftware().hasVersion() && !txcaps.getSoftware().getVersion().equals(capabilitiesStatementQuick.getSoftware().getVersion())) {
txcaps = null;
}
}
if (txcaps == null) {
txcaps = client.getTerminologyCapabilities();
if (txCache != null) {
txCache.cacheTerminologyCapabilities(getAddress(), txcaps);
}
}
if (txcaps != null) {
for (TerminologyCapabilitiesExpansionParameterComponent t : txcaps.getExpansion().getParameter()) {
if ("cache-id".equals(t.getName())) {
setTxCaching(true);
break;
}
}
}
initialised = true;
}
}
public boolean supportsSystem(String system) throws IOException {
initialize();
for (TerminologyCapabilitiesCodeSystemComponent tccs : txcaps.getCodeSystem()) {
if (system.equals(tccs.getUri())) {
return true;
}
}
return false;
}
}

View File

@ -19,6 +19,7 @@ import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
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.ValueSetUtilities;
@ -37,7 +38,7 @@ public class TerminologyClientManager {
}
public interface ITerminologyClientFactory {
ITerminologyClient makeClient(String id, String url, String userAgent) throws URISyntaxException;
ITerminologyClient makeClient(String id, String url, String userAgent, ToolingClientLogger logger) throws URISyntaxException;
String getVersion();
}
@ -47,7 +48,6 @@ public class TerminologyClientManager {
private ITerminologyClientFactory factory;
private String cacheId;
private boolean isTxCaching;
private List<TerminologyClientContext> serverList = new ArrayList<>(); // clients by server address
private Map<String, TerminologyClientContext> serverMap = new HashMap<>(); // clients by server address
private Map<String, String> resMap = new HashMap<>(); // client resolution list
@ -61,30 +61,18 @@ public class TerminologyClientManager {
private String monitorServiceURL;
public TerminologyClientManager(ITerminologyClientFactory factory) {
public TerminologyClientManager(ITerminologyClientFactory factory, String cacheId) {
super();
this.factory = factory;
this.cacheId = cacheId;
}
public String getCacheId() {
return cacheId;
}
public void setCacheId(String cacheId) {
this.cacheId = cacheId;
}
public boolean isTxCaching() {
return isTxCaching;
}
public void setTxCaching(boolean isTxCaching) {
this.isTxCaching = isTxCaching;
}
public void copy(TerminologyClientManager other) {
cacheId = other.cacheId;
isTxCaching = other.isTxCaching;
serverList.addAll(other.serverList);
serverMap.putAll(other.serverMap);
resMap.putAll(other.resMap);
@ -93,10 +81,6 @@ public class TerminologyClientManager {
usage = other.usage;
}
public boolean usingCache() {
return isTxCaching && cacheId != null;
}
public TerminologyClientContext chooseServer(Set<String> systems, boolean expand) throws TerminologyServiceException {
if (serverList.isEmpty()) {
@ -135,10 +119,11 @@ public class TerminologyClientManager {
TerminologyClientContext client = serverMap.get(server);
if (client == null) {
try {
client = new TerminologyClientContext(factory.makeClient("id"+(serverList.size()+1), server, getMasterClient().getUserAgent()), false);
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);
}
@ -152,7 +137,7 @@ public class TerminologyClientManager {
}
if (expParameters != null) {
if (!url.contains("|")) {
// the client hasn''t specified an explicit version, but the expansion parameters might
// 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();
@ -176,7 +161,7 @@ public class TerminologyClientManager {
for (JsonObject item : json.getJsonObjects("authoritative")) {
return item.asString("url");
}
for (JsonObject item : json.getJsonObjects("candidate")) {
for (JsonObject item : json.getJsonObjects("candidates")) {
return item.asString("url");
}
} catch (Exception e) {
@ -222,12 +207,14 @@ public class TerminologyClientManager {
}
}
public void setMasterClient(ITerminologyClient client) {
TerminologyClientContext details = new TerminologyClientContext(client, true);
public TerminologyClientContext setMasterClient(ITerminologyClient client) {
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() {
@ -246,13 +233,6 @@ public class TerminologyClientManager {
return map;
}
public TerminologyCapabilities getTxCapabilities() {
return hasClient() ? serverList.get(0).getTxcaps() : null;
}
public void setTxCapabilities(TerminologyCapabilities txCaps) {
serverList.get(0).setTxcaps(txCaps);
}
public void setFactory(ITerminologyClientFactory factory) {
this.factory = factory;
@ -309,8 +289,6 @@ public class TerminologyClientManager {
return monitorServiceURL;
}
public Parameters getExpansionParameters() {
return expParameters;
}
@ -343,7 +321,7 @@ public class TerminologyClientManager {
server = item.asString("url");
}
}
for (JsonObject item : json.getJsonObjects("candidate")) {
for (JsonObject item : json.getJsonObjects("candidates")) {
if (server == null) {
server = item.asString("url");
}
@ -360,17 +338,19 @@ public class TerminologyClientManager {
TerminologyClientContext client = serverMap.get(server);
if (client == null) {
try {
client = new TerminologyClientContext(factory.makeClient("id"+(serverList.size()+1), server, getMasterClient().getUserAgent()), false);
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("|") ?
"?url="+canonical.substring(0, canonical.lastIndexOf("|"))+"&version="+canonical.substring(canonical.lastIndexOf("|")+1) :
"?url="+canonical;
"?_format=json&url="+canonical.substring(0, canonical.lastIndexOf("|"))+"&version="+canonical.substring(canonical.lastIndexOf("|")+1) :
"?_format=json&url="+canonical;
request = Utilities.pathURL(client.getAddress(), "ValueSet"+ criteria);
Bundle bnd = client.getClient().search("ValueSet", criteria);
String rid = null;
if (bnd.getEntry().size() == 0) {
@ -395,15 +375,23 @@ public class TerminologyClientManager {
ValueSet vs = (ValueSet) client.getClient().read("ValueSet", rid);
return new SourcedValueSet(server, vs);
} catch (Exception e) {
e.printStackTrace();
String msg = "Error resolving valueSet "+canonical+": "+e.getMessage()+" ("+request+")";
if (!internalErrors.contains(msg)) {
internalErrors.add(msg);
}
if (!monitorServiceURL.contains("tx.fhir.org")) {
e.printStackTrace();
}
e.printStackTrace();
return null;
}
}
public boolean supportsSystem(String system) throws IOException {
for (TerminologyClientContext client : serverList) {
if (client.supportsSystem(system)) {
return true;
}
}
return false;
}
}

View File

@ -57,7 +57,7 @@ public class TerminologyClientR5 implements ITerminologyClient {
public static class TerminologyClientR5Factory implements ITerminologyClientFactory {
@Override
public ITerminologyClient makeClient(String id, String url, String userAgent) throws URISyntaxException {
public ITerminologyClient makeClient(String id, String url, String userAgent, ToolingClientLogger logger) throws URISyntaxException {
return new TerminologyClientR5(id, checkEndsWith("/r4", url), userAgent);
}
@ -146,6 +146,11 @@ public class TerminologyClientR5 implements ITerminologyClient {
return this;
}
@Override
public ToolingClientLogger getLogger() {
return client.getLogger();
}
@Override
public ITerminologyClient setLogger(ToolingClientLogger txLog) {
client.setLogger(txLog);

View File

@ -46,6 +46,7 @@ import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.context.ILoggingService.LogCategory;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
@ -60,6 +61,8 @@ import org.hl7.fhir.utilities.IniFile;
import org.hl7.fhir.utilities.StringPair;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.model.JsonNull;
import org.hl7.fhir.utilities.json.model.JsonProperty;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationOptions;
@ -98,6 +101,25 @@ public class TerminologyCache {
}
public static class SourcedValueSetEntry {
private String server;
private String filename;
public SourcedValueSetEntry(String server, String filename) {
super();
this.server = server;
this.filename = filename;
}
public String getServer() {
return server;
}
public String getFilename() {
return filename;
}
}
public static final boolean TRANSIENT = false;
public static final boolean PERMANENT = true;
private static final String NAME_FOR_NO_SYSTEM = "all-systems";
@ -106,6 +128,7 @@ public class TerminologyCache {
private static final String CACHE_FILE_EXTENSION = ".cache";
private static final String CAPABILITY_STATEMENT_TITLE = ".capabilityStatement";
private static final String TERMINOLOGY_CAPABILITIES_TITLE = ".terminologyCapabilities";
private static final String FIXED_CACHE_VERSION = "3"; // last change: when multiple servers were introduced
private SystemNameKeyGenerator systemNameKeyGenerator = new SystemNameKeyGenerator();
@ -213,10 +236,11 @@ public class TerminologyCache {
@Getter private int requestCount;
@Getter private int hitCount;
@Getter private int networkCount;
private CapabilityStatement capabilityStatementCache = null;
private TerminologyCapabilities terminologyCapabilitiesCache = null;
private Map<String, CapabilityStatement> capabilityStatementCache = new HashMap<>();
private Map<String, TerminologyCapabilities> terminologyCapabilitiesCache = new HashMap<>();
private Map<String, NamedCache> caches = new HashMap<String, NamedCache>();
private Map<String, StringPair> vsCache = new HashMap<>();
private Map<String, SourcedValueSetEntry> vsCache = new HashMap<>();
private Map<String, String> serverMap = new HashMap<>();
@Getter @Setter private static boolean noCaching;
@Getter @Setter private static boolean cacheErrors;
@ -226,54 +250,104 @@ public class TerminologyCache {
public TerminologyCache(Object lock, String folder) throws FileNotFoundException, IOException, FHIRException {
super();
this.lock = lock;
if (folder == null) {
folder = Utilities.path("[tmp]", "default-tx-cache");
}
this.folder = folder;
requestCount = 0;
hitCount = 0;
networkCount = 0;
File f = new File(folder);
if (!f.exists()) {
Utilities.createDirectory(folder);
}
if (!f.exists()) {
throw new IOException("Unable to create terminology cache at "+folder);
}
checkVersion();
load();
}
private void checkVersion() throws IOException {
File verFile = new File(Utilities.path(folder, "version.ctl"));
if (verFile.exists()) {
String ver = TextFile.fileToString(verFile);
if (!ver.equals(FIXED_CACHE_VERSION)) {
System.out.println("Terminology Cache Version has changed from 1 to "+FIXED_CACHE_VERSION+", so clearing txCache");
clear();
}
TextFile.stringToFile(FIXED_CACHE_VERSION, verFile);
} else {
TextFile.stringToFile(FIXED_CACHE_VERSION, verFile);
}
}
public String getServerId(String address) throws IOException {
if (serverMap.containsKey(address)) {
return serverMap.get(address);
}
String id = address.replace("http://", "").replace("https://", "").replace("/", ".");
int i = 1;
while (serverMap.containsValue(id)) {
i++;
id = address.replace("https:", "").replace("https:", "").replace("/", ".")+i;
}
serverMap.put(address, id);
if (folder != null) {
load();
IniFile ini = new IniFile(Utilities.path(folder, "servers.ini"));
ini.setStringProperty("servers", id, address, null);
ini.save();
}
return id;
}
public boolean hasCapabilityStatement() {
return capabilityStatementCache != null;
}
public CapabilityStatement getCapabilityStatement() {
return capabilityStatementCache;
}
public void cacheCapabilityStatement(CapabilityStatement capabilityStatement) {
if (noCaching) {
return;
}
this.capabilityStatementCache = capabilityStatement;
save(capabilityStatementCache, CAPABILITY_STATEMENT_TITLE);
}
public boolean hasTerminologyCapabilities() {
return terminologyCapabilitiesCache != null;
}
public TerminologyCapabilities getTerminologyCapabilities() {
return terminologyCapabilitiesCache;
}
public void cacheTerminologyCapabilities(TerminologyCapabilities terminologyCapabilities) {
if (noCaching) {
return;
}
this.terminologyCapabilitiesCache = terminologyCapabilities;
save(terminologyCapabilitiesCache, TERMINOLOGY_CAPABILITIES_TITLE);
}
public void clear() {
public void unload() {
// not useable after this is called
caches.clear();
vsCache.clear();
}
private void clear() throws IOException {
Utilities.clearDirectory(folder);
caches.clear();
vsCache.clear();
}
public boolean hasCapabilityStatement(String address) {
return capabilityStatementCache.containsKey(address);
}
public CapabilityStatement getCapabilityStatement(String address) {
return capabilityStatementCache.get(address);
}
public void cacheCapabilityStatement(String address, CapabilityStatement capabilityStatement) throws IOException {
if (noCaching) {
return;
}
this.capabilityStatementCache.put(address, capabilityStatement);
save(capabilityStatement, CAPABILITY_STATEMENT_TITLE+"."+getServerId(address));
}
public boolean hasTerminologyCapabilities(String address) {
return terminologyCapabilitiesCache.containsKey(address);
}
public TerminologyCapabilities getTerminologyCapabilities(String address) {
return terminologyCapabilitiesCache.get(address);
}
public void cacheTerminologyCapabilities(String address, TerminologyCapabilities terminologyCapabilities) throws IOException {
if (noCaching) {
return;
}
this.terminologyCapabilitiesCache.put(address, terminologyCapabilities);
save(terminologyCapabilities, TERMINOLOGY_CAPABILITIES_TITLE+"."+getServerId(address));
}
public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs, Parameters expParameters) {
try {
@ -640,14 +714,19 @@ public class TerminologyCache {
private void loadCapabilityCache(String fn) {
try {
String src = TextFile.fileToString(Utilities.path(folder, fn));
String serverId = Utilities.getFileNameForName(fn).replace(CACHE_FILE_EXTENSION, "");
serverId = serverId.substring(serverId.indexOf(".")+1);
serverId = serverId.substring(serverId.indexOf(".")+1);
String address = getServerForId(serverId);
if (address != null) {
JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(src);
Resource resource = new JsonParser().parse(o);
JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(src);
Resource resource = new JsonParser().parse(o);
if (fn.startsWith(CAPABILITY_STATEMENT_TITLE)) {
this.capabilityStatementCache = (CapabilityStatement) resource;
} else if (fn.startsWith(TERMINOLOGY_CAPABILITIES_TITLE)) {
this.terminologyCapabilitiesCache = (TerminologyCapabilities) resource;
if (fn.startsWith(CAPABILITY_STATEMENT_TITLE)) {
this.capabilityStatementCache.put(address, (CapabilityStatement) resource);
} else if (fn.startsWith(TERMINOLOGY_CAPABILITIES_TITLE)) {
this.terminologyCapabilitiesCache.put(address, (TerminologyCapabilities) resource);
}
}
} catch (Exception e) {
e.printStackTrace();
@ -655,6 +734,15 @@ public class TerminologyCache {
}
}
private String getServerForId(String serverId) {
for (String n : serverMap.keySet()) {
if (serverMap.get(n).equals(serverId)) {
return n;
}
}
return null;
}
private CacheEntry getCacheEntry(String request, String resultString) throws IOException {
CacheEntry ce = new CacheEntry();
ce.persistent = true;
@ -730,6 +818,13 @@ public class TerminologyCache {
}
private void load() throws FHIRException, IOException {
IniFile ini = new IniFile(Utilities.path(folder, "servers.ini"));
if (ini.hasSection("servers")) {
for (String n : ini.getPropertyNames("servers")) {
serverMap.put(ini.getStringProperty("servers", n), n);
}
}
for (String fn : new File(folder).list()) {
if (fn.endsWith(CACHE_FILE_EXTENSION) && !fn.equals("validation" + CACHE_FILE_EXTENSION)) {
try {
@ -743,13 +838,21 @@ public class TerminologyCache {
}
}
}
File fini = new File(Utilities.path(folder, "vs-external.ini"));
if (fini.exists()) {
IniFile ini = new IniFile(fini.getAbsolutePath());
for (String k : ini.getPropertyNames("valuesets")) {
String fn = ini.getStringProperty("valuesets", k);
vsCache.put(k, new StringPair(Utilities.noString(fn) ? null : fn, ini.getStringProperty("servers", k)));
try {
File f = new File(Utilities.path(folder, "vs-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()) {
vsCache.put(p.getName(), null);
} else {
org.hl7.fhir.utilities.json.model.JsonObject j = p.getValue().asJsonObject();
vsCache.put(p.getName(), new SourcedValueSetEntry(j.asString("filename"), j.asString("server")));
}
}
}
} catch (Exception e) {
System.out.println("Error loading vs external cache: "+e.getMessage());
}
}
@ -858,12 +961,12 @@ public class TerminologyCache {
}
public SourcedValueSet getValueSet(String canonical) {
StringPair sp = vsCache.get(canonical);
if (sp == null || sp.getName() == null) {
SourcedValueSetEntry sp = vsCache.get(canonical);
if (sp == null) {
return null;
} else {
try {
return new SourcedValueSet(sp.getValue(), (ValueSet) new JsonParser().parse(new FileInputStream(Utilities.path(folder, sp.getName()))));
return new SourcedValueSet(sp.getServer(), sp.getFilename() == null ? null : (ValueSet) new JsonParser().parse(new FileInputStream(Utilities.path(folder, sp.getFilename()))));
} catch (Exception e) {
return null;
}
@ -871,6 +974,9 @@ public class TerminologyCache {
}
public void cacheValueSet(String canonical, SourcedValueSet svs) {
if (canonical == null) {
return;
}
try {
if (svs == null) {
vsCache.put(canonical, null);
@ -878,16 +984,25 @@ public class TerminologyCache {
String uuid = Utilities.makeUuidLC();
String fn = "vs-"+uuid+".json";
new JsonParser().compose(new FileOutputStream(Utilities.path(folder, fn)), svs.getVs());
vsCache.put(canonical, new StringPair(fn, svs.getServer()));
vsCache.put(canonical, new SourcedValueSetEntry(svs.getServer(), fn));
}
File fini = new File(Utilities.path(folder, "vs-external.ini"));
IniFile ini = new IniFile(fini.getAbsolutePath());
org.hl7.fhir.utilities.json.model.JsonObject j = new org.hl7.fhir.utilities.json.model.JsonObject();
for (String k : vsCache.keySet()) {
ini.setStringProperty("valuesets", k, vsCache.get(k).getName(), null);
ini.setStringProperty("servers", k, vsCache.get(k).getValue(), null);
SourcedValueSetEntry sve = vsCache.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);
}
}
ini.save();
org.hl7.fhir.utilities.json.parser.JsonParser.compose(j, new File(Utilities.path(folder, "vs-externals.json")), true);
} catch (Exception e) {
e.printStackTrace();
}
}

View File

@ -1,5 +1,7 @@
package org.hl7.fhir.r5.terminologies.validation;
import java.io.IOException;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
@ -76,6 +78,7 @@ import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager;
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider;
import org.hl7.fhir.r5.terminologies.providers.SpecialCodeSystem;
@ -131,28 +134,28 @@ public class ValueSetValidator extends ValueSetProcessBase {
private ValidationContextCarrier localContext;
private List<CodeSystem> localSystems = new ArrayList<>();
protected Parameters expansionProfile;
private TerminologyCapabilities txCaps;
private TerminologyClientManager tcm;
private Set<String> unknownSystems;
private Set<String> unknownValueSets = new HashSet<>();
private boolean throwToServer;
public ValueSetValidator(IWorkerContext context, TerminologyOperationContext opContext, ValidationOptions options, ValueSet source, Parameters expansionProfile, TerminologyCapabilities txCaps) {
public ValueSetValidator(IWorkerContext context, TerminologyOperationContext opContext, ValidationOptions options, ValueSet source, Parameters expansionProfile, TerminologyClientManager tcm) {
super(context, opContext);
this.valueset = source;
this.options = options;
this.expansionProfile = expansionProfile;
this.txCaps = txCaps;
this.tcm = tcm;
analyseValueSet();
}
public ValueSetValidator(IWorkerContext context, TerminologyOperationContext opContext, ValidationOptions options, ValueSet source, ValidationContextCarrier ctxt, Parameters expansionProfile, TerminologyCapabilities txCaps) {
public ValueSetValidator(IWorkerContext context, TerminologyOperationContext opContext, ValidationOptions options, ValueSet source, ValidationContextCarrier ctxt, Parameters expansionProfile, TerminologyClientManager tcm) {
super(context, opContext);
this.valueset = source;
this.options = options.copy();
this.options.setEnglishOk(true);
this.localContext = ctxt;
this.expansionProfile = expansionProfile;
this.txCaps = txCaps;
this.tcm = tcm;
analyseValueSet();
}
@ -522,7 +525,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
if (cs == null) {
OpIssueCode oic = OpIssueCode.NotFound;
IssueType itype = IssueType.NOTFOUND;
ValueSet vs = context.findTxResource(ValueSet.class, system);
ValueSet vs = context.fetchResource(ValueSet.class, system);
if (vs != null) {
warningMessage = context.formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, system);
oic = OpIssueCode.InvalidData;
@ -716,14 +719,15 @@ public class ValueSetValidator extends ValueSetProcessBase {
if (SERVER_SIDE_LIST.contains(system)) {
return true;
}
if (txCaps != null) {
for (TerminologyCapabilitiesCodeSystemComponent tccs : txCaps.getCodeSystem()) {
if (system.equals(tccs.getUri())) {
return true;
}
try {
if (tcm.supportsSystem(system)) {
return true;
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
@ -1458,7 +1462,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
unknownValueSets.add(url);
info.addIssue(makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, null, context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_VALUE_SET_, url), OpIssueCode.NotFound, null));
}
ValueSetValidator vsc = new ValueSetValidator(context, opContext.copy(), options, vs, localContext, expansionProfile, txCaps);
ValueSetValidator vsc = new ValueSetValidator(context, opContext.copy(), options, vs, localContext, expansionProfile, tcm);
vsc.setThrowToServer(throwToServer);
inner.put(url, vsc);
return vsc;

View File

@ -95,7 +95,7 @@ public class SimpleWorkerContextTests {
public void beforeEach() {
Mockito.doReturn(DUMMY_URL).when(terminologyClient).getAddress();
context.txCache = terminologyCache;
context.initTxCache(terminologyCache);
context.expParameters = expParameters;
context.terminologyClientManager.setMasterClient(terminologyClient);
context.txLog = txLog;
@ -427,46 +427,41 @@ public class SimpleWorkerContextTests {
@Test
public void testInitializationWithCache() {
Mockito.doReturn(true).when(terminologyCache).hasTerminologyCapabilities();
Mockito.doReturn(true).when(terminologyCache).hasCapabilityStatement();
Mockito.doReturn(terminologyCapabilities).when(terminologyCache).getTerminologyCapabilities();
Mockito.doReturn(capabilitiesStatement).when(terminologyCache).getCapabilityStatement();
String actual = context.connectToTSServer(new TerminologyClientR5Factory(), terminologyClient, null);
assertEquals("dummyVersion", actual);
Mockito.verify(terminologyCache).getTerminologyCapabilities();
Mockito.verify(terminologyCache).getCapabilityStatement();
Mockito.verify(terminologyClient, times(0)).getTerminologyCapabilities();
Mockito.verify(terminologyClient, times(0)).getCapabilitiesStatementQuick();
Mockito.verify(context).setTxCaps(terminologyCapabilities);
// String address = "/...";
//
// Mockito.doReturn(true).when(terminologyCache).hasTerminologyCapabilities(address);
//// Mockito.doReturn(true).when(terminologyCache).hasCapabilityStatement();
//
// Mockito.doReturn(terminologyCapabilities).when(terminologyCache).getTerminologyCapabilities(address);
//// Mockito.doReturn(capabilitiesStatement).when(terminologyCache).getCapabilityStatement();
//
// context.connectToTSServer(new TerminologyClientR5Factory(), terminologyClient);
//
// Mockito.verify(terminologyCache).getTerminologyCapabilities(address);
// Mockito.verify(terminologyClient).getCapabilitiesStatementQuick();
//
// Mockito.verify(terminologyCache, times(0)).getCapabilityStatement(address);
// Mockito.verify(terminologyClient, times(0)).getTerminologyCapabilities();
}
@Test
public void testInitializationWithClient() {
// String address = "/...";
//
// Mockito.doReturn(false).when(terminologyCache).hasTerminologyCapabilities(address);
//// Mockito.doReturn(false).when(terminologyCache).hasCapabilityStatement();
//
// Mockito.doReturn(terminologyCapabilities).when(terminologyClient).getTerminologyCapabilities();
// Mockito.doReturn(capabilitiesStatement).when(terminologyClient).getCapabilitiesStatementQuick();
//
// context.connectToTSServer(new TerminologyClientR5Factory(), terminologyClient);
//
// Mockito.verify(terminologyCache, times(0)).getTerminologyCapabilities(address);
// Mockito.verify(terminologyCache, times(0)).getCapabilityStatement(address);
//
// Mockito.verify(terminologyClient).getTerminologyCapabilities();
// Mockito.verify(terminologyClient).getCapabilitiesStatementQuick();
Mockito.doReturn(false).when(terminologyCache).hasTerminologyCapabilities();
Mockito.doReturn(false).when(terminologyCache).hasCapabilityStatement();
Mockito.doReturn(terminologyCapabilities).when(terminologyClient).getTerminologyCapabilities();
Mockito.doReturn(capabilitiesStatement).when(terminologyClient).getCapabilitiesStatementQuick();
String actual = context.connectToTSServer(new TerminologyClientR5Factory(), terminologyClient, null);
assertEquals("dummyVersion", actual);
Mockito.verify(terminologyCache, times(0)).getTerminologyCapabilities();
Mockito.verify(terminologyCache, times(0)).getCapabilityStatement();
Mockito.verify(terminologyClient).getTerminologyCapabilities();
Mockito.verify(terminologyClient).getCapabilitiesStatementQuick();
Mockito.verify(context).setTxCaps(terminologyCapabilities);
}
public static Stream<Arguments> zipSlipData() {

View File

@ -31,6 +31,7 @@ import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache;
import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.tests.ResourceLoaderTests;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.junit.jupiter.api.Test;
@ -92,6 +93,8 @@ public class TerminologyCacheTests implements ResourceLoaderTests {
@Test
public void testCachePersistence() throws IOException, URISyntaxException {
String address = "/...";
Object lock = new Object();
Path tempCacheDirectory = createTempCacheDirectory();
ValueSet valueSet = new ValueSet();
@ -117,8 +120,8 @@ public class TerminologyCacheTests implements ResourceLoaderTests {
// Add dummy results to the cache
TerminologyCache terminologyCacheA = new TerminologyCache(lock, tempCacheDirectory.toString());
terminologyCacheA.cacheTerminologyCapabilities(terminologyCapabilities);
terminologyCacheA.cacheCapabilityStatement(capabilityStatement);
terminologyCacheA.cacheTerminologyCapabilities(address, terminologyCapabilities);
terminologyCacheA.cacheCapabilityStatement(address, capabilityStatement);
ValidationResult codingResultA = new ValidationResult(ValidationMessage.IssueSeverity.INFORMATION, "dummyInfo", null);
TerminologyCache.CacheToken codingTokenA = terminologyCacheA.generateValidationToken(CacheTestUtils.validationOptions,
@ -136,8 +139,8 @@ public class TerminologyCacheTests implements ResourceLoaderTests {
terminologyCacheA.cacheExpansion(expansionTokenA, expansionOutcomeA, true);
// Check that the in-memory cache is returning what we put in
{
assertEquals(terminologyCapabilities, terminologyCacheA.getTerminologyCapabilities());
assertEquals(capabilityStatement, terminologyCacheA.getCapabilityStatement());
assertEquals(terminologyCapabilities, terminologyCacheA.getTerminologyCapabilities(address));
assertEquals(capabilityStatement, terminologyCacheA.getCapabilityStatement(address));
assertValidationResultEquals(codingResultA, terminologyCacheA.getValidation(codingTokenA));
assertValidationResultEquals(codeableConceptResultA, terminologyCacheA.getValidation(codeableConceptTokenA));
@ -148,8 +151,8 @@ public class TerminologyCacheTests implements ResourceLoaderTests {
{
TerminologyCache terminologyCacheB = new TerminologyCache(lock, tempCacheDirectory.toString());
assertCanonicalResourceEquals(terminologyCapabilities, terminologyCacheB.getTerminologyCapabilities());
assertCanonicalResourceEquals(capabilityStatement, terminologyCacheB.getCapabilityStatement());
assertCanonicalResourceEquals(terminologyCapabilities, terminologyCacheB.getTerminologyCapabilities(address));
assertCanonicalResourceEquals(capabilityStatement, terminologyCacheB.getCapabilityStatement(address));
assertValidationResultEquals(codingResultA, terminologyCacheB.getValidation(terminologyCacheA.generateValidationToken(CacheTestUtils.validationOptions, coding, valueSet, new Parameters())));
assertValidationResultEquals(codeableConceptResultA, terminologyCacheB.getValidation(terminologyCacheA.generateValidationToken(CacheTestUtils.validationOptions, concept, valueSet, new Parameters())));

View File

@ -23,9 +23,11 @@ public class CacheVerificationLogger implements ToolingClientLogger {
System.err.println("Header: " + header);
}
}
System.err.println("Body");
System.err.println("----");
System.err.println(new String(body, StandardCharsets.UTF_8));
if (body != null) {
System.err.println("Body");
System.err.println("----");
System.err.println(new String(body, StandardCharsets.UTF_8));
}
}
requests++;
}

View File

@ -192,8 +192,8 @@ Terminology_TX_System_Relative = Coding.system must be an absolute reference, no
Terminology_TX_System_Unknown = Unknown Code System ''{0}''
Terminology_TX_System_ValueSet = Invalid System URI: {0} - cannot use a value set URI as a system
Terminology_TX_System_ValueSet2 = The Coding references a value set, not a code system (''{0}'')
Terminology_TX_ValueSet_NotFound = ValueSet {0} not found
Terminology_TX_ValueSet_NotFound_CS = Found a reference to a CodeSystem ({0}) where a ValueSet belongs
Terminology_TX_ValueSet_NotFound = ValueSet ''{0}'' not found
Terminology_TX_ValueSet_NotFound_CS = Found a reference to a CodeSystem ''{0}'' where a ValueSet belongs
Type_Specific_Checks_DT_Base64_Valid = The value ''{0}'' is not a valid Base64 value
Type_Specific_Checks_DT_Boolean_Value = Boolean values must be ''true'' or ''false''
Type_Specific_Checks_DT_Code_WS = The code ''{0}'' is not valid (whitespace rules)
@ -458,7 +458,7 @@ Delimited_versions_have_exact_match_for_delimiter____vs_ = Delimited versions ha
Duplicate_Resource_ = Duplicate Resource {0} of type {3} (existing version {2}, new version {1})
DUPLICATE_RESOURCE_VERSION = Duplicate Resource {0} Version {1} of type {2}
Error_expanding_ValueSet_running_without_terminology_services = Error expanding ValueSet: running without terminology services
Error_validating_code_running_without_terminology_services = Error validating code: running without terminology services
Error_validating_code_running_without_terminology_services = Unable to validate code ''{0}'' in system ''{1}'' because the validator is running without terminology services
Unable_to_validate_code_without_using_server = Unable to validate code without using server because: {0}
UNABLE_TO_VALIDATE_LOCALLY = Unable to validate code locally: {0}
Profile___Error_generating_snapshot = Profile {0} ({1}). Error generating snapshot

View File

@ -475,7 +475,6 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
protected void initContext(TimeTracker tt) throws IOException {
context.setCanNoTS(true);
context.setCacheId(UUID.randomUUID().toString());
context.setAllowLoadingDuplicates(true); // because of Forge
context.setExpansionParameters(makeExpProfile());
if (tt != null) {
@ -524,7 +523,8 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
} else {
try {
TerminologyClientFactory factory = new TerminologyClientFactory(version);
return context.connectToTSServer(factory, factory.makeClient("Tx-Server", url, context.getUserAgent()), log);
context.connectToTSServer(factory, url, context.getUserAgent(), log);
return "Connected to Terminology Server at "+url;
} catch (Exception e) {
if (context.isCanRunWithoutTerminology()) {
return "n/a: Running without Terminology Server (error: " + e.getMessage() + ")";

View File

@ -289,7 +289,7 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
@Nonnull
protected ITerminologyClient getTerminologyClient(String root) throws URISyntaxException {
return new TerminologyClientFactory(context.getVersion()).makeClient("source", root, Common.getValidatorUserAgent());
return new TerminologyClientFactory(context.getVersion()).makeClient("source", root, Common.getValidatorUserAgent(), null);
}
private String getRoot(String[] p, String url) {

View File

@ -1175,6 +1175,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return false;
}
} else if (context.isNoTerminologyServer() && NO_TX_SYSTEM_EXEMPT.contains(system)) {
txWarning(errors, NO_RULE_DATE, null, IssueType.BUSINESSRULE, element.line(), element.col(), path, false, I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES, code, system);
return ok; // no checks in this case
} else if (startsWithButIsNot(system, "http://snomed.info/sct", "http://loinc.org", "http://unitsofmeasure.org", "http://www.nlm.nih.gov/research/umls/rxnorm")) {
rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_INVALID, system);

View File

@ -139,7 +139,7 @@ public class TxTester {
private ITerminologyClient connectToServer() throws URISyntaxException {
System.out.println("Connect to "+server);
return new TerminologyClientFactory(FhirPublication.R4).makeClient("Test-Server", server, "Tools/Java");
return new TerminologyClientFactory(FhirPublication.R4).makeClient("Test-Server", server, "Tools/Java", null);
}