From 42a9e4737988473ef7469835bf0dabb0d39712de Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sun, 2 Apr 2023 23:07:34 +1000 Subject: [PATCH] Add terminology service tests --- .../fhir/r5/context/BaseWorkerContext.java | 4 +- .../fhir/r5/elementmodel/LangaugeUtils.java | 55 +++++ .../org/hl7/fhir/r5/model/Parameters.java | 10 + .../r5/terminologies/ValueSetExpander.java | 3 + .../terminologies/ValueSetExpanderSimple.java | 135 +++++++++--- .../fhir/r5/test/utils/CompareUtilities.java | 20 +- .../validation/testexecutor/TestModules.java | 2 +- .../tests/TerminologyServiceTests.java | 207 ++++++++++++++++++ 8 files changed, 401 insertions(+), 35 deletions(-) create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/LangaugeUtils.java create mode 100644 org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/TerminologyServiceTests.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 93e7d77f5..08997d456 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 @@ -820,7 +820,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte return res; } } - p.setParameter("includeDefinition", false); + p.setParameter("excludeNested", !hierarchical); if (incompleteOk) { p.setParameter("incomplete-ok", true); @@ -872,7 +872,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId()); } catch (Exception e) { - res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors).setTxLink(txLog == null ? null : txLog.getLastId()); + res = new ValueSetExpansionOutcome((e.getMessage() == null ? e.getClass().getName() : e.getMessage()), TerminologyServiceErrorClass.UNKNOWN, allErrors).setTxLink(txLog == null ? null : txLog.getLastId()); } txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT); return res; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/LangaugeUtils.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/LangaugeUtils.java new file mode 100644 index 000000000..7d04b330d --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/LangaugeUtils.java @@ -0,0 +1,55 @@ +package org.hl7.fhir.r5.elementmodel; + +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.utilities.i18n.LanguageFileProducer; +import org.hl7.fhir.utilities.i18n.LanguageFileProducer.LanguageProducerLanguageSession; +import org.hl7.fhir.utilities.i18n.LanguageFileProducer.TextUnit; + +/** + * in here: + * * generateTranslations + * * importFromTranslations + * * stripTranslations + * * switchLanguage + * + * in the validator + * + * @author grahamegrieve + * generateTranslations = -langTransform export -src {src} -tgt {tgt} -dest {dest} + * importFromTranslations = -langTransform import -src {src} -tgt {tgt} -dest {dest} + */ +public class LangaugeUtils { + + IWorkerContext context; + + + public LangaugeUtils(IWorkerContext context) { + super(); + this.context = context; + } + + public void generateTranslations(Element resource, LanguageProducerLanguageSession session) { + translate(resource, session); + } + + private void translate(Element element, LanguageProducerLanguageSession langSession) { + if (element.isPrimitive() && isTranslatable(element)) { + String base = element.primitiveValue(); + if (base != null) { + String translation = element.getTranslation(langSession.getTargetLang()); + langSession.entry(new TextUnit(element.getPath(), base, translation)); + } + } + for (Element c: element.getChildren()) { + translate(c, langSession); + } + } + + private boolean isTranslatable(Element element) { + return element.getProperty().isTranslatable(); + } + + public static boolean matches(String dstLang, String srcLang) { + return dstLang == null ? false : dstLang.equals(srcLang); + } +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Parameters.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Parameters.java index aca8100d9..832327f08 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Parameters.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Parameters.java @@ -1810,5 +1810,15 @@ public boolean getParameterBool(String name) { } // end addition +public String getParameterString(String name) { + for (ParametersParameterComponent p : getParameter()) { + if (p.getName().equals(name)) { + if (p.getValue() instanceof PrimitiveType) + return ((PrimitiveType) p.getValue()).primitiveValue(); + } + } + return null; +} + } 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 d7d39cfde..b76f48d22 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 @@ -102,6 +102,9 @@ public interface ValueSetExpander { if (!allErrors.contains(error)) { allErrors.add(error); } + if (!errList.contains(error)) { + errList.add(error); + } } public ValueSet getValueset() { 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 982dde93f..4777f5ac3 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 @@ -81,6 +81,8 @@ import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.exceptions.NoTerminologyServiceException; import org.hl7.fhir.exceptions.TerminologyServiceException; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.elementmodel.LangaugeUtils; +import org.hl7.fhir.r5.extensions.Extensions; import org.hl7.fhir.r5.model.BooleanType; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode; @@ -98,6 +100,7 @@ import org.hl7.fhir.r5.model.PackageInformation; import org.hl7.fhir.r5.model.Parameters; import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.r5.model.PrimitiveType; +import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.UriType; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; @@ -117,6 +120,12 @@ import com.google.errorprone.annotations.NoAllocation; public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetExpander { + public interface IConceptFilter { + + boolean includeConcept(CodeSystem cs, ConceptDefinitionComponent def); + + } + public class PropertyFilter implements IConceptFilter { private ConceptSetFilterComponent filter; @@ -172,12 +181,20 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx } } - public interface IConceptFilter { + public class RegexFilter implements IConceptFilter { - boolean includeConcept(CodeSystem cs, ConceptDefinitionComponent def); + private String regex; + + protected RegexFilter(String regex) { + super(); + this.regex = regex; + } + @Override + public boolean includeConcept(CodeSystem cs, ConceptDefinitionComponent def) { + return def.getCode().matches(regex); + } } - private List codes = new ArrayList(); private List roots = new ArrayList(); private Map map = new HashMap(); @@ -208,8 +225,9 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx maxExpansionSize = theMaxExpansionSize; } - private ValueSetExpansionContainsComponent addCode(String system, String code, String display, ValueSetExpansionContainsComponent parent, List designations, Parameters expParams, - boolean isAbstract, boolean inactive, List filters, boolean noInactive, boolean deprecated, List vsProp) { + private ValueSetExpansionContainsComponent addCode(String system, String code, String display, String dispLang, ValueSetExpansionContainsComponent parent, List designations, Parameters expParams, + boolean isAbstract, boolean inactive, String definition, List filters, boolean noInactive, boolean deprecated, List vsProp, + List csProps, List expProps) { if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code)) return null; @@ -227,10 +245,30 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx if (deprecated) { ValueSetUtilities.setDeprecated(vsProp, n); } + if (expParams.getParameterBool("includeDefinition") && definition != null) { + n.addExtension(Extensions.makeVSConceptDefinition(definition)); + } + + // display and designations + String srcLang = dispLang; + String dstLang = focus.getLanguage(); + boolean usedDisplay = false; + ConceptDefinitionDesignationComponent tu = expParams.hasParameter("displayLanguage") ? getMatchingLang(designations, expParams.getParameterString("displayLanguage")) : null; + if (tu != null) { + n.setDisplay(tu.getValue()); + } else if (display != null && (srcLang == null || dstLang == null || LangaugeUtils.matches(dstLang, srcLang))) { + n.setDisplay(display); + usedDisplay = true; + } else { + // we don't have a usable display + } if (expParams.getParameterBool("includeDesignations") && designations != null) { + if (!usedDisplay && display != null) { + n.addDesignation().setLanguage(srcLang).setValue(display); + } for (ConceptDefinitionDesignationComponent t : designations) { - if (t.getLanguage() != null || t.getValue() != null) { + if (t != tu && (t.hasLanguage() || t.hasUse()) && t.getValue() != null) { ConceptReferenceDesignationComponent d = n.addDesignation(); if (t.getLanguage() != null) { d.setLanguage(t.getLanguage().trim()); @@ -238,14 +276,32 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx if (t.getValue() != null) { d.setValue(t.getValue().trim()); } + if (t.getUse() != null) { + d.setUse(t.getUse()); + } } } } - ConceptDefinitionDesignationComponent t = expParams.hasLanguage() ? getMatchingLang(designations, expParams.getLanguage()) : null; - if (t == null) - n.setDisplay(display); - else - n.setDisplay(t.getValue()); + for (ParametersParameterComponent p : expParams.getParameter()) { + if ("property".equals(p.getName())) { + if (csProps != null && p.hasValue()) { + for (ConceptPropertyComponent cp : csProps) { + if (p.getValue().primitiveValue().equals(cp.getCode())) { + n.addProperty().setCode(cp.getCode()).setValue(cp.getValue()); + } + } + } + if (expProps != null && p.hasValue()) { + for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent cp : expProps) { + if (p.getValue().primitiveValue().equals(cp.getCode())) { + n.addProperty(cp); + } + } + } + } + } + + String s = key(n); if (map.containsKey(s) || excludeKeys.contains(s)) { @@ -290,12 +346,12 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx return null; } - private void addCodeAndDescendents(ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List filters, boolean noInactive, List vsProps) throws FHIRException { + private void addCodeAndDescendents(ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List filters, boolean noInactive, List vsProps, ValueSet vsSrc) throws FHIRException { focus.checkNoModifiers("Expansion.contains", "expanding"); - ValueSetExpansionContainsComponent np = addCode(focus.getSystem(), focus.getCode(), focus.getDisplay(), parent, - convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), filters, noInactive, false, vsProps); + ValueSetExpansionContainsComponent np = addCode(focus.getSystem(), focus.getCode(), focus.getDisplay(), vsSrc.getLanguage(), parent, + convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), focus.getExtensionString(ToolingExtensions.EXT_DEFINITION), filters, noInactive, false, vsProps, null, focus.getProperty()); for (ValueSetExpansionContainsComponent c : focus.getContains()) - addCodeAndDescendents(focus, np, expParams, filters, noInactive, vsProps); + addCodeAndDescendents(focus, np, expParams, filters, noInactive, vsProps, vsSrc); } private List convert(List designations) { @@ -310,7 +366,8 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx return list; } - private void addCodeAndDescendents(CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List filters, ConceptDefinitionComponent exclusion, IConceptFilter filterFunc, boolean noInactive, List vsProps) throws FHIRException { + private void addCodeAndDescendents(CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List filters, + ConceptDefinitionComponent exclusion, IConceptFilter filterFunc, boolean noInactive, List vsProps) throws FHIRException { def.checkNoModifiers("Code in Code System", "expanding"); if (exclusion != null) { if (exclusion.getCode().equals(def.getCode())) @@ -321,7 +378,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx boolean inc = CodeSystemUtilities.isInactive(cs, def); boolean dep = CodeSystemUtilities.isDeprecated(cs, def, false); if ((includeAbstract || !abs) && filterFunc.includeConcept(cs, def)) { - np = addCode(system, def.getCode(), def.getDisplay(), parent, def.getDesignation(), expParams, abs, inc, filters, noInactive, dep, vsProps); + np = addCode(system, def.getCode(), def.getDisplay(), cs.getLanguage(), parent, def.getDesignation(), expParams, abs, inc, def.getDefinition(), filters, noInactive, dep, vsProps, def.getProperty(), null); } for (ConceptDefinitionComponent c : def.getConcept()) { addCodeAndDescendents(cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps); @@ -334,7 +391,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx } - private void addCodes(ValueSetExpansionComponent expand, List params, Parameters expParams, List filters, boolean noInactive, List vsProps) throws ETooCostly, FHIRException { + private void addCodes(ValueSetExpansionComponent expand, List params, Parameters expParams, List filters, boolean noInactive, List vsProps, ValueSet vsSrc) throws ETooCostly, FHIRException { if (expand != null) { if (expand.getContains().size() > maxExpansionSize) throw failCostly("Too many codes to display (>" + Integer.toString(expand.getContains().size()) + ")"); @@ -343,7 +400,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx params.add(p); } - copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps); + copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps, vsSrc); } } @@ -432,8 +489,12 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx focus.getExpansion().setTimestampElement(DateTimeType.now()); focus.getExpansion().setIdentifier(Factory.createUUID()); for (ParametersParameterComponent p : expParams.getParameter()) { - if (Utilities.existsInList(p.getName(), "includeDesignations", "excludeNested", "activeOnly")) + if (Utilities.existsInList(p.getName(), "includeDesignations", "excludeNested", "activeOnly")) { focus.getExpansion().addParameter().setName(p.getName()).setValue(p.getValue()); + } + } + if (expParams.hasLanguage()) { + focus.setLanguage(expParams.getLanguage()); } if (source.hasCompose()) { @@ -591,11 +652,12 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx } } - private void copyImportContains(List list, ValueSetExpansionContainsComponent parent, Parameters expParams, List filter, boolean noInactive, List vsProps) throws FHIRException { + private void copyImportContains(List list, ValueSetExpansionContainsComponent parent, Parameters expParams, List filter, boolean noInactive, List vsProps, ValueSet vsSrc) throws FHIRException { for (ValueSetExpansionContainsComponent c : list) { c.checkNoModifiers("Imported Expansion in Code System", "expanding"); - ValueSetExpansionContainsComponent np = addCode(c.getSystem(), c.getCode(), c.getDisplay(), parent, null, expParams, c.getAbstract(), c.getInactive(), filter, noInactive, false, vsProps); - copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps); + ValueSetExpansionContainsComponent np = addCode(c.getSystem(), c.getCode(), c.getDisplay(), vsSrc.getLanguage(), parent, null, expParams, c.getAbstract(), c.getInactive(), c.getExtensionString(ToolingExtensions.EXT_DEFINITION), + filter, noInactive, false, vsProps, null, c.getProperty()); + copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc); } } @@ -612,13 +674,13 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx ValueSet base = imports.get(0); imports.remove(0); base.checkNoModifiers("Imported ValueSet", "expanding"); - copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty()); + copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base); } else { CodeSystem cs = context.fetchCodeSystem(inc.getSystem()); if (isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) { doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, valueSet.getExpansion().getProperty()); } else { - doInternalIncludeCodes(inc, exp, expParams, imports, cs, noInactive); + doInternalIncludeCodes(inc, exp, expParams, imports, cs, noInactive, valueSet); } } } @@ -653,7 +715,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx } } for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { - addCodeAndDescendents(cc, null, expParams, imports, noInactive, vsProps); + addCodeAndDescendents(cc, null, expParams, imports, noInactive, vsProps, vs); } } @@ -666,7 +728,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx return false; } - public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List imports, CodeSystem cs, boolean noInactive) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException { + public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List imports, CodeSystem cs, boolean noInactive, Resource vsSrc) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException { if (cs == null) { if (context.isNoTerminologyServer()) throw failTSE("Unable to find code system " + inc.getSystem().toString()); @@ -712,7 +774,8 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx } else { inactive = CodeSystemUtilities.isInactive(cs, def); } - addCode(inc.getSystem(), c.getCode(), !Utilities.noString(c.getDisplay()) ? c.getDisplay() : def == null ? null : def.getDisplay(), null, convertDesignations(c.getDesignation()), expParams, false, inactive, imports, noInactive, false, exp.getProperty()); + addCode(inc.getSystem(), c.getCode(), !Utilities.noString(c.getDisplay()) ? c.getDisplay() : def == null ? null : def.getDisplay(), c.hasDisplay() ? vsSrc.getLanguage() : cs.getLanguage(), null, mergeDesignations(def, convertDesignations(c.getDesignation())), + expParams, false, inactive, def == null ? null : def.getDefinition(), imports, noInactive, false, exp.getProperty(), def != null ? def.getProperty() : null, null); } } if (inc.getFilter().size() > 1) { @@ -758,8 +821,8 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx if (def != null) { if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) { if (def.getDisplay().contains(fc.getValue())) { - addCode(inc.getSystem(), def.getCode(), def.getDisplay(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def), - imports, noInactive, false, exp.getProperty()); + addCode(inc.getSystem(), def.getCode(), def.getDisplay(), cs.getLanguage(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def), + def.getDefinition(), imports, noInactive, false, exp.getProperty(), def.getProperty(), null); } } } @@ -767,12 +830,24 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx for (ConceptDefinitionComponent def : cs.getConcept()) { addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null, new PropertyFilter(fc, getPropertyDefinition(cs, fc.getProperty())), noInactive, exp.getProperty()); } + } else if ("regex".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) { + for (ConceptDefinitionComponent def : cs.getConcept()) { + addCodeAndDescendents(cs, inc.getSystem(), def, null, expParams, imports, null, new RegexFilter(fc.getValue()), noInactive, exp.getProperty()); + } } else { throw fail("Search by property[" + fc.getProperty() + "] and op[" + fc.getOp() + "] is not supported yet"); } } } + private List mergeDesignations(ConceptDefinitionComponent def, + List list) { + List res = new ArrayList<>(); + res.addAll(def.getDesignation()); + res.addAll(list); + return res; + } + private PropertyComponent getPropertyDefinition(CodeSystem cs, String property) { for (PropertyComponent cp : cs.getProperty()) { if (cp.getCode().equals(property)) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java index 63c88095d..e9bd3c532 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java @@ -2,6 +2,7 @@ package org.hl7.fhir.r5.test.utils; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.utilities.CSFile; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.ToolGlobalSettings; @@ -181,7 +182,7 @@ public class CompareUtilities extends BaseTestingUtilities { String diff = null; if (System.getProperty("os.name").contains("Linux")) diff = Utilities.path("/", "usr", "bin", "meld"); - else { + else if (System.getenv("ProgramFiles(X86)") != null) { if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge"), "\\WinMergeU.exe", null)) diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe"); else if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "Meld"), "\\Meld.exe", null)) @@ -271,7 +272,7 @@ public class CompareUtilities extends BaseTestingUtilities { String actualJsonString = actualJsonPrimitive.getAsString(); String expectedJsonString = expectedJsonPrimitive.getAsString(); if (!(actualJsonString.contains(" JUNIT4_CLASSNAMES = Arrays.asList("org.hl7.fhir.validation.tests.ValidationTests"); + public static final List JUNIT4_CLASSNAMES = Arrays.asList("org.hl7.fhir.validation.tests.ValidationTests", "org.hl7.fhir.terminologies.tests.TerminologyServiceTests"); } diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/TerminologyServiceTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/TerminologyServiceTests.java new file mode 100644 index 000000000..1f8007a41 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/TerminologyServiceTests.java @@ -0,0 +1,207 @@ +package org.hl7.fhir.terminology.tests; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50; +import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50; +import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50; +import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; +import org.hl7.fhir.exceptions.DefinitionException; +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.formats.JsonParser; +import org.hl7.fhir.r5.formats.XmlParser; +import org.hl7.fhir.r5.model.Constants; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent; +import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; +import org.hl7.fhir.r5.test.utils.CompareUtilities; +import org.hl7.fhir.r5.test.utils.TestingUtilities; +import org.hl7.fhir.terminology.tests.TerminologyServiceTests.JsonObjectPair; +import org.hl7.fhir.utilities.FhirPublication; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.json.model.JsonObject; +import org.hl7.fhir.validation.ValidationEngine; +import org.hl7.fhir.validation.tests.ValidationEngineTests; +import org.hl7.fhir.validation.tests.utilities.TestUtilities; +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.google.common.base.Charsets; + + + +@RunWith(Parameterized.class) +public class TerminologyServiceTests { + + public static class JsonObjectPair { + public JsonObjectPair(JsonObject suite, JsonObject test) { + this.suite = suite; + this.test = test; + } + private JsonObject suite; + private JsonObject test; + } + + @Parameters(name = "{index}: id {0}") + public static Iterable data() throws IOException { + + String contents = TestingUtilities.loadTestResource("tx", "test-cases.json"); + + Map examples = new HashMap(); + manifest = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(contents); + for (org.hl7.fhir.utilities.json.model.JsonObject suite : manifest.getJsonObjects("suites")) { + String sn = suite.asString("name"); + for (org.hl7.fhir.utilities.json.model.JsonObject test : suite.getJsonObjects("tests")) { + String tn = test.asString("name"); + examples.put(sn+"."+tn, new JsonObjectPair(suite, test)); + } + } + + List names = new ArrayList(examples.size()); + names.addAll(examples.keySet()); + Collections.sort(names); + + List objects = new ArrayList(examples.size()); + for (String id : names) { + objects.add(new Object[]{id, examples.get(id)}); + } + return objects; + } + + private static org.hl7.fhir.utilities.json.model.JsonObject manifest; + private JsonObjectPair setup; + private String version; + private String name; + + + private static ValidationEngine baseEngine; + + public TerminologyServiceTests(String name, JsonObjectPair setup) { + this.name = name; + this.setup = setup; + version = "5.0.0"; + } + + @SuppressWarnings("deprecation") + @Test + public void test() throws Exception { + if (baseEngine == null) { + baseEngine = TestUtilities.getValidationEngine("hl7.fhir.r5.core#5.0.0", ValidationEngineTests.DEF_TX, null, FhirPublication.R5, true, "5.0.0"); + } + ValidationEngine engine = new ValidationEngine(this.baseEngine); + for (String s : setup.suite.forceArray("setup").asStrings()) { + Resource res = loadResource(s); + engine.seeResource(res); + } + Resource req = loadResource(setup.test.asString("request")); + String resp = TestingUtilities.loadTestResource("tx", setup.test.asString("response")); + if (setup.test.asString("operation").equals("expand")) { + expand(engine, req, resp, setup.test.asString("response")); + } else if (setup.test.asString("operation").equals("validate-code")) { + validate(engine, req, resp); + } else { + Assertions.fail("Unknown Operation "+setup.test.asString("operation")); + } + } + + private void expand(ValidationEngine engine, Resource req, String resp, String fn) throws IOException { + String fp = Utilities.path("[tmp]", "tx", fn); + File fo = new File(fp); + if (fo.exists()) { + fo.delete(); + } + + org.hl7.fhir.r5.model.Parameters p = ( org.hl7.fhir.r5.model.Parameters) req; + ValueSet vs = engine.getContext().fetchResource(ValueSet.class, p.getParameterValue("url").primitiveValue()); + boolean hierarchical = p.hasParameter("excludeNested") ? p.getParameterBool("excludeNested") == false : true; + Assertions.assertNotNull(vs); + ValueSetExpansionOutcome vse = engine.getContext().expandVS(vs, false, hierarchical, false, p); + if (resp.contains("\"ValueSet\"")) { + if (vse.getValueset() == null) { + Assertions.fail(vse.getError()); + } else { + if (!p.hasParameter("excludeNested")) { + removeParameter(vse.getValueset(), "excludeNested"); + } + String vsj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(vse.getValueset()); + String diff = CompareUtilities.checkJsonSrcIsSame(resp, vsj); + if (diff != null) { + Utilities.createDirectory(Utilities.getDirectoryForFile(fp)); + TextFile.stringToFile(vsj, fp); + } + Assertions.assertTrue(diff == null, diff); + } + } else { + Assertions.fail("expand error not done yet"); + } + } + + private void removeParameter(ValueSet valueset, String name) { + for (ValueSetExpansionParameterComponent exp : valueset.getExpansion().getParameter()) { + if (exp.getName().equals(name)) { + valueset.getExpansion().getParameter().remove(exp); + return; + } + } + } + + private void validate(ValidationEngine engine2, Resource req, String resp) { + Assertions.fail("validate not done yet"); + } + + public Resource loadResource(String filename) throws IOException, FHIRFormatError, FileNotFoundException, FHIRException, DefinitionException { + String contents = TestingUtilities.loadTestResource("tx", filename); + try (InputStream inputStream = IOUtils.toInputStream(contents, Charsets.UTF_8)) { + if (filename.contains(".json")) { + if (Constants.VERSION.equals(version) || "5.0".equals(version)) + return new JsonParser().parse(inputStream); + else if (org.hl7.fhir.dstu3.model.Constants.VERSION.equals(version) || "3.0".equals(version)) + return VersionConvertorFactory_30_50.convertResource(new org.hl7.fhir.dstu3.formats.JsonParser().parse(inputStream)); + else if (org.hl7.fhir.dstu2016may.model.Constants.VERSION.equals(version) || "1.4".equals(version)) + return VersionConvertorFactory_14_50.convertResource(new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(inputStream)); + else if (org.hl7.fhir.dstu2.model.Constants.VERSION.equals(version) || "1.0".equals(version)) + return VersionConvertorFactory_10_50.convertResource(new org.hl7.fhir.dstu2.formats.JsonParser().parse(inputStream)); + else if (org.hl7.fhir.r4.model.Constants.VERSION.equals(version) || "4.0".equals(version)) + return VersionConvertorFactory_40_50.convertResource(new org.hl7.fhir.r4.formats.JsonParser().parse(inputStream)); + else + throw new FHIRException("unknown version " + version); + } else { + if (Constants.VERSION.equals(version) || "5.0".equals(version)) + return new XmlParser().parse(inputStream); + else if (org.hl7.fhir.dstu3.model.Constants.VERSION.equals(version) || "3.0".equals(version)) + return VersionConvertorFactory_30_50.convertResource(new org.hl7.fhir.dstu3.formats.XmlParser().parse(inputStream)); + else if (org.hl7.fhir.dstu2016may.model.Constants.VERSION.equals(version) || "1.4".equals(version)) + return VersionConvertorFactory_14_50.convertResource(new org.hl7.fhir.dstu2016may.formats.XmlParser().parse(inputStream)); + else if (org.hl7.fhir.dstu2.model.Constants.VERSION.equals(version) || "1.0".equals(version)) + return VersionConvertorFactory_10_50.convertResource(new org.hl7.fhir.dstu2.formats.XmlParser().parse(inputStream)); + else if (org.hl7.fhir.r4.model.Constants.VERSION.equals(version) || "4.0".equals(version)) + return VersionConvertorFactory_40_50.convertResource(new org.hl7.fhir.r4.formats.XmlParser().parse(inputStream)); + else + throw new FHIRException("unknown version " + version); + } + } + } + + @AfterClass + public static void saveWhenDone() throws IOException { + + } + +} \ No newline at end of file