From c2214884630800256a25449af3084db82731014c Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 18 Mar 2024 14:35:46 +1100 Subject: [PATCH 1/3] fix rendering of contained resources in Kindling --- .../hl7/fhir/r5/renderers/ProfileDrivenRenderer.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java index 529555498..8a8ce24a6 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java @@ -833,19 +833,12 @@ public class ProfileDrivenRenderer extends ResourceRenderer { if ("DomainResource.contained".equals(child.getBase().getPath())) { if (round2) { for (BaseWrapper v : p.getValues()) { - if (v.getBase() != null && !RendererFactory.hasSpecificRenderer(v.fhirType())) { + if (v.getResource() != null && !RendererFactory.hasSpecificRenderer(v.fhirType())) { x.hr(); RenderingContext ctxt = context.copy(); ctxt.setContained(true); ResourceRenderer rnd = RendererFactory.factory(v.fhirType(), ctxt); - ResourceWrapper rw = null; - if (v.getBase() instanceof org.hl7.fhir.r5.elementmodel.Element) { - rw = new ElementWrappers.ResourceWrapperMetaElement(ctxt, (org.hl7.fhir.r5.elementmodel.Element) v.getBase()); - } else if (v.getBase() instanceof Resource){ - rw = new DirectWrappers.ResourceWrapperDirect(ctxt, (Resource) v.getBase()); - } else { - throw new FHIRException("Not handled: base = "+v.getBase().getClass().getName()); - } + ResourceWrapper rw = v.getResource(); rnd.render(x.blockquote(), rw); } } From 413c047b561dbfe972096ed9e63b2e2a6fa93237 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 18 Mar 2024 14:35:59 +1100 Subject: [PATCH 2/3] more fixing rendering --- .../hl7/fhir/r5/renderers/utils/BaseWrappers.java | 1 + .../hl7/fhir/r5/renderers/utils/DOMWrappers.java | 13 +++++++++++++ .../hl7/fhir/r5/renderers/utils/DirectWrappers.java | 6 ++++++ .../fhir/r5/renderers/utils/ElementWrappers.java | 5 +++++ 4 files changed, 25 insertions(+) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java index 67a09994d..66ba376f7 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java @@ -62,6 +62,7 @@ public class BaseWrappers { public interface BaseWrapper extends WrapperBase { public Base getBase() throws UnsupportedEncodingException, IOException, FHIRException; + public ResourceWrapper getResource() throws UnsupportedEncodingException, IOException, FHIRException; // for contained, etc public PropertyWrapper getChildByName(String tail); public String fhirType(); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java index d62204ca6..8f33c2980 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java @@ -102,6 +102,19 @@ public class DOMWrappers { return type; } + @Override + public ResourceWrapper getResource() throws UnsupportedEncodingException, IOException, FHIRException { + Element r = XMLUtil.getFirstChild(element); + StructureDefinition sd = getContext().getContext().fetchTypeDefinition(r.getLocalName()); + if (sd == null) { + throw new FHIRException("Unable to find definition for type "+type+" @ "+definition.getPath()); + } + if (sd.getKind() != StructureDefinitionKind.RESOURCE) { + throw new FHIRException("Definition for type "+type+" is not for a resource @ "+definition.getPath()); + } + return new ResourceWrapperElement(context, r, sd); + } + } public static class PropertyWrapperElement extends RendererWrapperImpl implements PropertyWrapper { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java index 37304c3a9..b7c72d40f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java @@ -5,6 +5,7 @@ import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.DomainResource; @@ -164,6 +165,11 @@ public class DirectWrappers { return wrapped.fhirType(); } + @Override + public ResourceWrapper getResource() throws UnsupportedEncodingException, IOException, FHIRException { + return new DirectWrappers.ResourceWrapperDirect(getContext(), (Resource) wrapped); + } + } public static class ResourceWrapperDirect extends WrapperBaseImpl implements ResourceWrapper { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java index 568344a5f..fcdb83fae 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java @@ -109,6 +109,11 @@ public class ElementWrappers { return element.fhirType(); } + @Override + public ResourceWrapper getResource() throws UnsupportedEncodingException, IOException, FHIRException { + return new ElementWrappers.ResourceWrapperMetaElement(getContext(), element); + } + } public static class ResourceWrapperMetaElement extends WrapperBaseImpl implements ResourceWrapper { From 30545d31c178814ed528500b27a0ad32e17e1333 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 18 Mar 2024 18:09:56 +1100 Subject: [PATCH 3/3] CodeSystem property validation improvements --- .../r5/terminologies/CodeSystemUtilities.java | 12 +++++ .../org/hl7/fhir/utilities/Utilities.java | 7 +++ .../fhir/utilities/i18n/I18nConstants.java | 1 + .../src/main/resources/Messages.properties | 1 + .../instance/InstanceValidator.java | 4 +- .../instance/type/CodeSystemValidator.java | 51 ++++++++++++++++++- 6 files changed, 72 insertions(+), 4 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java index dcd0ab1bd..6a35595db 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java @@ -234,6 +234,15 @@ public class CodeSystemUtilities extends TerminologyUtilities { concept.addProperty().setCode(code).setValue(value); } + public static void setProperty(CodeSystem cs, ConceptDefinitionComponent concept, String url, String code, DataType value) throws FHIRFormatError { + defineProperty(cs, code, propertyTypeForValue(value), url); + ConceptPropertyComponent p = getProperty(concept, code); + if (p != null) + p.setValue(value); + else + concept.addProperty().setCode(code).setValue(value); + } + private static PropertyType propertyTypeForValue(DataType value) { if (value instanceof BooleanType) { @@ -262,6 +271,9 @@ public class CodeSystemUtilities extends TerminologyUtilities { private static String defineProperty(CodeSystem cs, String code, PropertyType pt) { String url = "http://hl7.org/fhir/concept-properties#"+code; + return defineProperty(cs, code, pt, url); + } + private static String defineProperty(CodeSystem cs, String code, PropertyType pt, String url) { for (PropertyComponent p : cs.getProperty()) { if (p.hasCode() && p.getCode().equals(code)) { if (!p.getUri().equals(url)) { diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java index 07af9940b..74ce1b74f 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java @@ -2235,4 +2235,11 @@ public class Utilities { return true; } + public static String stripEoln(String text) { + if (text == null) { + return ""; + } + return text.replace("\r\n", " ").replace("\n", " ").replace("\r", " "); + } + } 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 15a3dfa0b..2364dce33 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 @@ -1101,6 +1101,7 @@ public class I18nConstants { public static final String VALUESET_BAD_FILTER_VALUE_HAS_COMMA = "VALUESET_BAD_FILTER_VALUE_HAS_COMMA"; public static final String VALUESET_BAD_FILTER_VALUE_VALID_REGEX = "VALUESET_BAD_FILTER_VALUE_VALID_REGEX"; public static final String VALUESET_BAD_PROPERTY_NO_REGEX = "VALUESET_BAD_PROPERTY_NO_REGEX"; + public static final String CODESYSTEM_PROPERTY_CODE_WARNING = "CODESYSTEM_PROPERTY_CODE_WARNING"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index aeacaf818..383fcef75 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -1159,4 +1159,5 @@ VALUESET_BAD_FILTER_OP = The operation ''{0}'' is not allowed for property ''{1} VALUESET_BAD_FILTER_VALUE_HAS_COMMA = The filter value has a comma, but the operation is different to 'in' and 'not-in', so the comma will be interpreted as part of the {0} value VALUESET_BAD_FILTER_VALUE_VALID_REGEX = The value for a filter based on property ''{0}'' should be a valid regex, not ''{1}'' (err = ''{2}'') VALUESET_BAD_PROPERTY_NO_REGEX = Cannot apply a regex filter to the property ''{0}'' (usually regex filters are applied to the codes, or a named property of the code system) +CODESYSTEM_PROPERTY_CODE_WARNING = If the type is ''code'', then the valueSet property should be provided to clarify what kind of code will be found in the element diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index b5d1b884d..58331f512 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -3344,7 +3344,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat Set refs = new HashSet<>(); int count = countTargetMatches(resource, ref, true, "$", refs); if (count == 0) { - rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_RESOLVE, href, xpath, node.allText()); + rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_RESOLVE, href, xpath, Utilities.stripEoln(node.allText())); } else if (count > 1) { warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_MULTIPLE_MATCHES, href, xpath, node.allText(), CommaSeparatedStringBuilder.join(", ", refs)); } @@ -5760,7 +5760,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } - if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wg != null && !url.contains("http://hl7.org/fhir/sid"), I18nConstants.VALIDATION_HL7_WG_NEEDED, ToolingExtensions.EXT_WORKGROUP)) { + if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wg != null || url.contains("http://hl7.org/fhir/sid"), I18nConstants.VALIDATION_HL7_WG_NEEDED, ToolingExtensions.EXT_WORKGROUP)) { HL7WorkGroup wgd = HL7WorkGroups.find(wg); if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wgd != null, I18nConstants.VALIDATION_HL7_WG_UNKNOWN, wg)) { String rpub = "HL7 International / "+wgd.getName(); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java index 27e77526b..91c26bcdc 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java @@ -14,6 +14,7 @@ import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; @@ -57,16 +58,30 @@ public class CodeSystemValidator extends BaseValidator { } + public enum CodeValidationRule { + NO_VALIDATION, INTERNAL_CODE, VS_ERROR, VS_WARNING + } + public class PropertyDef { private String uri; private String code; private String type; + + private CodeValidationRule rule; + private String valueset; + protected PropertyDef(String uri, String code, String type) { super(); this.uri = uri; this.code = code; this.type = type; } + + public void setCodeValidationRules(CodeValidationRule rule, String valueset) { + this.rule = rule; + this.valueset = valueset; + } + public String getUri() { return uri; } @@ -76,9 +91,14 @@ public class CodeSystemValidator extends BaseValidator { public String getType() { return type; } + public String getValueset() { + return valueset; + } } + private static final String VS_PROP_STATUS = null; + public CodeSystemValidator(BaseValidator parent) { super(parent); } @@ -252,8 +272,7 @@ public class CodeSystemValidator extends BaseValidator { ukp = KnownProperty.ItemWeight; break; default: - ok = false; - rule(errors, "2024-03-06", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_PROPERTY_BAD_HL7_URI, uri); + ok = rule(errors, "2024-03-06", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), isBaseSpec(cs.getNamedChildValue("url")), I18nConstants.CODESYSTEM_PROPERTY_BAD_HL7_URI, uri); } } } @@ -309,7 +328,31 @@ public class CodeSystemValidator extends BaseValidator { if (type != null) { ok = rule(errors, "2024-03-06", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), type.equals(ukp.getType()), I18nConstants.CODESYSTEM_PROPERTY_URI_TYPE_MISMATCH, uri, ukp.getType(),type) && ok; } + switch (ukp) { + case Child: + case Parent: + case PartOf: + case Synonym: + pd.setCodeValidationRules(CodeValidationRule.INTERNAL_CODE, null); + break; + case Status: + pd.setCodeValidationRules(CodeValidationRule.VS_WARNING, VS_PROP_STATUS); + break; + default: + break; + } + } else if ("code".equals(pd.getType())) { + if (property.hasExtension("http://hl7.org/fhir/6.0/StructureDefinition/extension-CodeSystem.property.valueSet")) { + pd.setCodeValidationRules(CodeValidationRule.VS_ERROR, property.getExtensionValue("http://hl7.org/fhir/6.0/StructureDefinition/extension-CodeSystem.property.valueSet").primitiveValue()); + } else if (VersionUtilities.isR6Plus(context.getVersion())) { + hint(errors, "2024-03-18", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), ukp != null && type.equals(ukp.getType()), I18nConstants.CODESYSTEM_PROPERTY_CODE_WARNING); + } else { + + } + } else if ("Coding".equals(pd.getType()) && property.hasExtension("http://hl7.org/fhir/6.0/StructureDefinition/extension-CodeSystem.property.valueSet")) { + pd.setCodeValidationRules(CodeValidationRule.VS_ERROR, property.getExtensionValue("http://hl7.org/fhir/6.0/StructureDefinition/extension-CodeSystem.property.valueSet").primitiveValue()); } + if (uri == null) { if (ckp == null) { hint(errors, "2024-03-06", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_PROPERTY_UNKNOWN_CODE, code); @@ -323,6 +366,10 @@ public class CodeSystemValidator extends BaseValidator { return ok; } + private boolean isBaseSpec(String url) { + return url.startsWith("http://hl7.org/fhir/") && !url.substring(20).contains("/"); + } + private boolean checkConcept(List errors, Element cs, NodeStack stack, boolean caseSensitive, String hierarchyMeaning, CodeSystem csB, Element concept, Set codes, Map properties) { boolean ok = true; String code = concept.getNamedChildValue("code");