From 10d560c85965d446f8f7b104ce2a0514ee667d9c Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 10 Mar 2023 21:28:08 +1100 Subject: [PATCH] Add support for locally processed special case code systems (and an example - rgb) --- .../fhir/r5/context/BaseWorkerContext.java | 23 +++++--- .../r5/renderers/StructureMapRenderer.java | 4 +- .../r5/terminologies/CodeSystemProvider.java | 37 ++++++++++++ .../terminologies/ValueSetCheckerSimple.java | 59 +++++++++++-------- .../r5/terminologies/ValueSetExpander.java | 2 +- .../terminologies/ValueSetExpanderSimple.java | 21 +++++-- .../CodeSystemProviderExtension.java | 9 +++ .../providers/ColorRGBProvider.java | 28 +++++++++ .../fhir/utilities/i18n/I18nConstants.java | 1 + .../src/main/resources/Messages.properties | 1 + 10 files changed, 144 insertions(+), 41 deletions(-) create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemProvider.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/providers/CodeSystemProviderExtension.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/providers/ColorRGBProvider.java diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java index 5126e13c0..b614bf7d2 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java @@ -827,21 +827,26 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte // ok, first we try to expand locally ValueSetExpanderSimple vse = constructValueSetExpanderSimple(); + res = null; try { res = vse.expand(vs, p); - allErrors.addAll(vse.getAllErrors()); - if (res.getValueset() != null) { - if (!res.getValueset().hasUrl()) { - throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET)); - } - txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT); - return res; - } } catch (Exception e) { allErrors.addAll(vse.getAllErrors()); e.printStackTrace(); + res = new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN); } - + allErrors.addAll(vse.getAllErrors()); + if (res.getValueset() != null) { + if (!res.getValueset().hasUrl()) { + throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET)); + } + txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT); + return res; + } + if (res.getErrorClass() == TerminologyServiceErrorClass.INTERNAL_ERROR) { // this class is created specifically to say: don't consult the server + return new ValueSetExpansionOutcome(res.getError(), res.getErrorClass()); + } + // if that failed, we try to expand on the server if (addDependentResources(p, vs)) { p.addParameter().setName("cache-id").setValue(new StringType(cacheId)); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureMapRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureMapRenderer.java index 5ef569996..50886c746 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureMapRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureMapRenderer.java @@ -343,7 +343,7 @@ public class StructureMapRenderer extends TerminologyRenderer { } } if (r.getTarget().size() > 1) { - x.b().tx(" -> "); + x.color(COLOR_SYNTAX).b().tx(" -> "); boolean first = true; for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) { if (first) @@ -360,7 +360,7 @@ public class StructureMapRenderer extends TerminologyRenderer { renderTarget(x, rt, false); } } else if (r.hasTarget()) { - x.b().tx(" -> "); + x.color(COLOR_SYNTAX).b().tx(" -> "); renderTarget(x, r.getTarget().get(0), canBeAbbreviated); } if (r.hasRule()) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemProvider.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemProvider.java new file mode 100644 index 000000000..4c7b7fcca --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemProvider.java @@ -0,0 +1,37 @@ +package org.hl7.fhir.r5.terminologies; + +import java.util.List; + +import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; +import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; +import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension; +import org.hl7.fhir.r5.terminologies.providers.ColorRGBProvider; + +/** + * For special code systems where the code system resource isn't enough, but we can support them internall + * Usually, small grammar based code systems + * + * @author grahamegrieve + * + */ +public abstract class CodeSystemProvider { + + public static CodeSystemProvider factory(String system) { + switch (system) { + case "http://hl7.org/fhir/color-rgb" : return new ColorRGBProvider(); + default: + return null; + } + } + + public abstract void includeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp, + List imports, Parameters expParams, List extensions, boolean noInactive, + List vsProps) throws CodeSystemProviderExtension; + + protected abstract Boolean checkCode(String code); + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java index cb6822889..3f9f9df50 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java @@ -601,39 +601,50 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe problems.add(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_NO_SYSTEM, valueset.getVersionedUrl(), i, vsi.getSystem())); return false; } - CodeSystem cs = resolveCodeSystem(vsi.getSystem(), vsi.getVersion()); - if (cs != null && cs.getContent() == CodeSystemContentMode.COMPLETE) { + CodeSystemProvider csp = CodeSystemProvider.factory(vsi.getSystem()); + if (csp != null) { + Boolean ok = csp.checkCode(code); + if (ok == null) { + problems.add(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM_SYSTEM_IS_INDETERMINATE, valueset.getVersionedUrl(), vsi.getSystem())); + sys.add(vsi.getSystem()); + } else if (ok) { + sys.add(vsi.getSystem()); + } + } else { + CodeSystem cs = resolveCodeSystem(vsi.getSystem(), vsi.getVersion()); + if (cs != null && cs.getContent() == CodeSystemContentMode.COMPLETE) { - if (vsi.hasConcept()) { + if (vsi.hasConcept()) { + for (ConceptReferenceComponent cc : vsi.getConcept()) { + boolean match = cs.getCaseSensitive() ? cc.getCode().equals(code) : cc.getCode().equalsIgnoreCase(code); + if (match) { + sys.add(vsi.getSystem()); + } + } + } else { + ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code); + if (cc != null) { + sys.add(vsi.getSystem()); + } + } + } else if (vsi.hasConcept()) { for (ConceptReferenceComponent cc : vsi.getConcept()) { - boolean match = cs.getCaseSensitive() ? cc.getCode().equals(code) : cc.getCode().equalsIgnoreCase(code); + boolean match = cc.getCode().equals(code); if (match) { sys.add(vsi.getSystem()); } } } else { - ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code); - if (cc != null) { - sys.add(vsi.getSystem()); - } - } - } else if (vsi.hasConcept()) { - for (ConceptReferenceComponent cc : vsi.getConcept()) { - boolean match = cc.getCode().equals(code); - if (match) { - sys.add(vsi.getSystem()); - } - } - } else { - // we'll try to expand this one then - ValueSetExpansionOutcome vse = context.expandVS(vsi, false, false); - if (vse.isOk()) { - if (!checkSystems(vse.getValueset().getExpansion().getContains(), code, sys, problems)) { + // we'll try to expand this one then + ValueSetExpansionOutcome vse = context.expandVS(vsi, false, false); + if (vse.isOk()) { + if (!checkSystems(vse.getValueset().getExpansion().getContains(), code, sys, problems)) { + return false; + } + } else { + problems.add(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_UNKNOWN_SYSTEM, valueset.getVersionedUrl(), i, vsi.getSystem(), vse.getAllErrors().toString())); return false; } - } else { - problems.add(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_UNKNOWN_SYSTEM, valueset.getVersionedUrl(), i, vsi.getSystem(), vse.getAllErrors().toString())); - return false; } } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetExpander.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetExpander.java index 5582c30b4..d7d39cfde 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetExpander.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetExpander.java @@ -41,7 +41,7 @@ import org.hl7.fhir.r5.model.ValueSet; public interface ValueSetExpander { public enum TerminologyServiceErrorClass { - UNKNOWN, NOSERVICE, SERVER_ERROR, VALUESET_UNSUPPORTED, CODESYSTEM_UNSUPPORTED, BLOCKED_BY_OPTIONS; + UNKNOWN, NOSERVICE, SERVER_ERROR, VALUESET_UNSUPPORTED, CODESYSTEM_UNSUPPORTED, BLOCKED_BY_OPTIONS, INTERNAL_ERROR; public boolean isInfrastructure() { return this == NOSERVICE || this == SERVER_ERROR || this == VALUESET_UNSUPPORTED; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetExpanderSimple.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetExpanderSimple.java index 8accb44a3..747e6d1a1 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetExpanderSimple.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetExpanderSimple.java @@ -109,6 +109,7 @@ import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; +import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.utilities.Utilities; @@ -407,6 +408,10 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set // that might fail too, but it might not, later. return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.NOSERVICE, allErrors); + } catch (CodeSystemProviderExtension e) { + // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set + // that might fail too, but it might not, later. + return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.INTERNAL_ERROR, allErrors); } catch (Exception e) { // well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set // that might fail too, but it might not, later. @@ -414,11 +419,11 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx } } - public ValueSetExpansionOutcome expandInternal(ValueSet source, Parameters expParams) throws FHIRException, FileNotFoundException, ETooCostly, IOException { + public ValueSetExpansionOutcome expandInternal(ValueSet source, Parameters expParams) throws FHIRException, FileNotFoundException, ETooCostly, IOException, CodeSystemProviderExtension { return doExpand(source, expParams); } - public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException { + public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException, CodeSystemProviderExtension { if (expParams == null) expParams = makeDefaultExpansion(); source.checkNoModifiers("ValueSet", "expanding"); @@ -474,7 +479,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx } private void handleCompose(ValueSetComposeComponent compose, ValueSetExpansionComponent exp, Parameters expParams, String ctxt, List extensions, ValueSet valueSet) - throws ETooCostly, FileNotFoundException, IOException, FHIRException { + throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension { compose.checkNoModifiers("ValueSet.compose", "expanding"); // Exclude comes first because we build up a map of things to exclude for (ConceptSetComponent inc : compose.getExclude()) @@ -594,7 +599,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx } } - private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List extensions, ValueSet valueSet) throws ETooCostly, FileNotFoundException, IOException, FHIRException { + private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List extensions, ValueSet valueSet) throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension { inc.checkNoModifiers("Compose.include", "expanding"); List imports = new ArrayList(); for (UriType imp : inc.getValueSet()) { @@ -618,7 +623,13 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx } } - private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp, List imports, Parameters expParams, List extensions, boolean noInactive, List vsProps) throws FHIRException { + private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp, List imports, Parameters expParams, List extensions, boolean noInactive, List vsProps) throws FHIRException, CodeSystemProviderExtension { + CodeSystemProvider csp = CodeSystemProvider.factory(inc.getSystem()); + if (csp != null) { + csp.includeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, vsProps); + return; + } + ValueSetExpansionOutcome vso = context.expandVS(inc, heirarchical, noInactive); if (vso.getError() != null) { throw failTSE("Unable to expand imported value set: " + vso.getError()); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/providers/CodeSystemProviderExtension.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/providers/CodeSystemProviderExtension.java new file mode 100644 index 000000000..e1c718c87 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/providers/CodeSystemProviderExtension.java @@ -0,0 +1,9 @@ +package org.hl7.fhir.r5.terminologies.providers; + +public class CodeSystemProviderExtension extends Exception { + + public CodeSystemProviderExtension(String msg) { + super(msg); + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/providers/ColorRGBProvider.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/providers/ColorRGBProvider.java new file mode 100644 index 000000000..add98c894 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/providers/ColorRGBProvider.java @@ -0,0 +1,28 @@ +package org.hl7.fhir.r5.terminologies.providers; + +import java.util.List; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; +import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; +import org.hl7.fhir.r5.terminologies.CodeSystemProvider; + +public class ColorRGBProvider extends CodeSystemProvider { + + @Override + public void includeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp, + List imports, Parameters expParams, List extensions, boolean noInactive, + List vsProps) throws CodeSystemProviderExtension { + throw new CodeSystemProviderExtension("There are 16777216 colors, so the full list of colors is not displayed"); + } + + @Override + protected Boolean checkCode(String code) { + return code.matches("^\\#[0-9a-fA-F]{6}$"); + } + +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index 7f09dceb7..d047763c3 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -849,6 +849,7 @@ public class I18nConstants { public static final String SM_GROUP_NAME_DUPLICATE = "SM_GROUP_NAME_DUPLICATE"; public static final String CONCEPTMAP_GROUP_SOURCE_INCOMPLETE = "CONCEPTMAP_GROUP_SOURCE_INCOMPLETE"; public static final String CONCEPTMAP_GROUP_TARGET_INCOMPLETE = "CONCEPTMAP_GROUP_TARGET_INCOMPLETE"; + public static final String UNABLE_TO_RESOLVE_SYSTEM_SYSTEM_IS_INDETERMINATE = "UNABLE_TO_RESOLVE_SYSTEM_SYSTEM_IS_INDETERMINATE"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 184a7dcad..4e18e76f7 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -470,6 +470,7 @@ Coding_has_no_system__cannot_validate = Coding has no system - cannot validate Unable_to_handle_system__concept_filter_with_op__ = Unable to handle system {0} concept filter with op = {1} Unable_to_handle_system__filter_with_property__ = Unable to handle system {0} filter with property = {1} Unable_to_resolve_system__value_set_has_include_with_no_system = Unable to resolve system - value set {0} include #{1} has no system +UNABLE_TO_RESOLVE_SYSTEM_SYSTEM_IS_INDETERMINATE = The code system {1} referred to from value set {0} has a grammar, and the code might be valid in it Unable_to_resolve_system__value_set_has_include_with_unknown_system = Unable to resolve system - value set {0} include #{1} has system {2} which is unknown, and the server return error {3} Unable_to_resolve_system__value_set_has_include_with_filter = Unable to resolve system - value set {0} include #{1} has a filter on system {2} Unable_to_resolve_system__value_set_has_imports = Unable to resolve system - value set has imports