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 be75b0df8..07d5d6354 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 @@ -1029,7 +1029,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat validateResource(new ValidationContext(appContext, element), errors, element, element, defn, resourceIdRule, stack.resetIds(), null, new ValidationMode(ValidationReason.Validation, ProfileSource.ConfigProfile), false, false); } } - if (hintAboutNonMustSupport) { + if (hintAboutNonMustSupport && !profiles.isEmpty()) { checkElementUsage(errors, element, stack); } codingObserver.finish(errors, stack); @@ -1042,10 +1042,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private void checkElementUsage(List errors, Element element, NodeStack stack) { - String elementUsage = element.getUserString("elementSupported"); - hint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), elementUsage == null || elementUsage.equals("Y"), I18nConstants.MUSTSUPPORT_VAL_MUSTSUPPORT, element.getName(), element.getProperty().getStructure().getVersionedUrl()); + if (element.getPath()==null + || (element.getName().equals("id") && !element.getPath().substring(0, element.getPath().length()-3).contains(".")) + || (element.getName().equals("text") && !element.getPath().substring(0, element.getPath().length()-5).contains("."))) + return; + String hasFixed = element.getUserString("hasFixed"); + if (element.getPath().contains(".") && (hasFixed== null || !hasFixed.equals("Y"))) { + String elementUsage = element.getUserString("elementSupported"); + hint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), elementUsage != null && (elementUsage.equals("Y") || elementUsage.equals("NA")), I18nConstants.MUSTSUPPORT_VAL_MUSTSUPPORT, element.getName(), element.getProperty().getStructure().getVersionedUrl()); + if (elementUsage==null || !elementUsage.equals("Y")) + return; + } - if (element.hasChildren()) { + if (element.hasChildren() && (hasFixed== null || !hasFixed.equals("Y"))) { String prevName = ""; int elementCount = 0; for (Element ce : element.getChildren()) { @@ -6745,13 +6754,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } profile.setUserData("usesMustSupport", usesMustSupport); } - if (usesMustSupport.equals("Y")) { - String elementSupported = ei.getElement().getUserString("elementSupported"); - if (elementSupported == null || ei.definition.getMustSupport()) - if (ei.definition.getMustSupport()) { - ei.getElement().setUserData("elementSupported", "Y"); - } - } + String elementSupported = ei.getElement().getUserString("elementSupported"); + String fixedValue = ei.getElement().getUserString("hasFixed"); + if ((elementSupported == null || !elementSupported.equals("Y")) && ei.definition.getMustSupport()) { + if (ei.definition.getMustSupport()) { + ei.getElement().setUserData("elementSupported", "Y"); + } + } else if (elementSupported == null && !usesMustSupport.equals("Y")) + ei.getElement().setUserData("elementSupported", "NA"); + if (fixedValue==null && (ei.definition.hasFixed() || ei.definition.hasPattern())) + ei.getElement().setUserData("hasFixed", "Y"); } public boolean checkCardinalities(List errors, StructureDefinition profile, Element element, NodeStack stack, diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/profile/ProfileValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/profile/ProfileValidator.java index 32316e4f9..a02dd685d 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/profile/ProfileValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/profile/ProfileValidator.java @@ -1,5 +1,5 @@ -package org.hl7.fhir.validation.profile; - +package org.hl7.fhir.validation.profile; + /* Copyright (c) 2011+, HL7, Inc. All rights reserved. @@ -28,138 +28,140 @@ package org.hl7.fhir.validation.profile; POSSIBILITY OF SUCH DAMAGE. */ - - -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.List; - -import org.hl7.fhir.r5.context.IWorkerContext; -import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; -import org.hl7.fhir.r5.model.ElementDefinition; -import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; -import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; -import org.hl7.fhir.r5.utils.XVerExtensionManager; -import org.hl7.fhir.utilities.Utilities; -import org.hl7.fhir.utilities.validation.ValidationMessage; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; -import org.hl7.fhir.validation.BaseValidator; - -public class ProfileValidator extends BaseValidator { - - private boolean checkAggregation = false; - private boolean checkMustSupport = false; - private boolean allowDoubleQuotesInFHIRPath = false; - private FHIRPathEngine fpe; - - public ProfileValidator(IWorkerContext context, XVerExtensionManager xverManager) { - super(context, xverManager, false); - fpe = new FHIRPathEngine(context); - fpe.setAllowDoubleQuotes(allowDoubleQuotesInFHIRPath); - } - - public boolean isCheckAggregation() { - return checkAggregation; - } - - public boolean isCheckMustSupport() { - return checkMustSupport; - } - - public void setCheckAggregation(boolean checkAggregation) { - this.checkAggregation = checkAggregation; - } - - public void setCheckMustSupport(boolean checkMustSupport) { - this.checkMustSupport = checkMustSupport; - } - - public boolean isAllowDoubleQuotesInFHIRPath() { - return allowDoubleQuotesInFHIRPath; - } - - public void setAllowDoubleQuotesInFHIRPath(boolean allowDoubleQuotesInFHIRPath) { - this.allowDoubleQuotesInFHIRPath = allowDoubleQuotesInFHIRPath; - } - - protected boolean rule(List errors, IssueType type, String path, boolean b, String msg) { - String rn = path.contains(".") ? path.substring(0, path.indexOf(".")) : path; - return super.ruleHtml(errors, NO_RULE_DATE, type, path, b, msg, ""+rn+": "+Utilities.escapeXml(msg)); - } - - public List validate(StructureDefinition profile, boolean forBuild) { - List errors = new ArrayList(); - - // must have a FHIR version- GF#3160 - String s = (profile.getKind() == StructureDefinitionKind.LOGICAL) ? "Logical Models" : "Profiles"; - warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getUrl(), profile.hasFhirVersion(), s+" SHOULD state the FHIR Version on which they are based"); - warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getUrl(), profile.hasVersion(), s+" SHOULD state their own version"); - - // extensions must be defined - for (ElementDefinition ec : profile.getDifferential().getElement()) - checkExtensions(profile, errors, "differential", ec); - rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, profile.getId(), profile.hasSnapshot(), "missing Snapshot at "+profile.getName()+"."+profile.getName()); - for (ElementDefinition ec : profile.getSnapshot().getElement()) - checkExtensions(profile, errors, "snapshot", ec); - - if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, profile.getId(), profile.hasSnapshot(), "A snapshot is required")) { - Hashtable snapshotElements = new Hashtable(); - for (ElementDefinition ed : profile.getSnapshot().getElement()) { - snapshotElements.put(ed.getId(), ed); - for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { - if (forBuild) { - if (!inExemptList(inv.getKey())) { -// if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getId()+"::"+ed.getPath()+"::"+inv.getKey(), inv.hasExpression(), "The invariant has no FHIR Path expression ("+inv.getXpath()+")")) { -// try { -// fpe.check(null, profile.getType(), ed.getPath(), inv.getExpression()); // , inv.hasXpath() && inv.getXpath().startsWith("@value") -// } catch (Exception e) { -// // rule(errors, UNKNOWN_DATE_TIME, IssueType.STRUCTURE, profile.getId()+"::"+ed.getPath()+"::"+inv.getId(), false, e.getMessage()); -// } -// } - } - } - } - } - if (snapshotElements != null) { - for (ElementDefinition diffElement : profile.getDifferential().getElement()) { - if (diffElement == null) - throw new Error("Diff Element is null - this is not an expected thing"); - ElementDefinition snapElement = snapshotElements.get(diffElement.getId()); - if (snapElement!=null) { // Happens with profiles in the main build - should be able to fix once snapshot generation is fixed - Lloyd - warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, diffElement.getId(), !checkMustSupport || snapElement.hasMustSupport(), "Elements included in the differential should declare mustSupport"); - if (checkAggregation) { - for (TypeRefComponent type : snapElement.getType()) { - if ("http://hl7.org/fhir/Reference".equals(type.getWorkingCode()) || "http://hl7.org/fhir/canonical".equals(type.getWorkingCode())) { - warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, diffElement.getId(), type.hasAggregation(), "Elements with type Reference or canonical should declare aggregation"); - } - } - } - } - } - } - } - return errors; - } - - // these are special cases - private boolean inExemptList(String key) { - return key.startsWith("txt-"); - } - - private boolean checkExtensions(StructureDefinition profile, List errors, String kind, ElementDefinition ec) { - if (!ec.getType().isEmpty() && "Extension".equals(ec.getType().get(0).getWorkingCode()) && ec.getType().get(0).hasProfile()) { - String url = ec.getType().get(0).getProfile().get(0).getValue(); - StructureDefinition defn = context.fetchResource(StructureDefinition.class, url); - if (defn == null) { - defn = getXverExt(profile, errors, url); - } - return rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getId(), defn != null, "Unable to find Extension '"+url+"' referenced at "+profile.getUrl()+" "+kind+" "+ec.getPath()+" ("+ec.getSliceName()+")"); - } else { - return true; - } - } - - + + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; + +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; +import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; +import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; +import org.hl7.fhir.r5.utils.XVerExtensionManager; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; +import org.hl7.fhir.validation.BaseValidator; + +public class ProfileValidator extends BaseValidator { + + private boolean checkAggregation = false; + private boolean checkMustSupport = false; + private boolean allowDoubleQuotesInFHIRPath = false; + private FHIRPathEngine fpe; + + public ProfileValidator(IWorkerContext context, XVerExtensionManager xverManager) { + super(context, xverManager, false); + fpe = new FHIRPathEngine(context); + fpe.setAllowDoubleQuotes(allowDoubleQuotesInFHIRPath); + } + + public boolean isCheckAggregation() { + return checkAggregation; + } + + public boolean isCheckMustSupport() { + return checkMustSupport; + } + + public void setCheckAggregation(boolean checkAggregation) { + this.checkAggregation = checkAggregation; + } + + public void setCheckMustSupport(boolean checkMustSupport) { + this.checkMustSupport = checkMustSupport; + } + + public boolean isAllowDoubleQuotesInFHIRPath() { + return allowDoubleQuotesInFHIRPath; + } + + public void setAllowDoubleQuotesInFHIRPath(boolean allowDoubleQuotesInFHIRPath) { + this.allowDoubleQuotesInFHIRPath = allowDoubleQuotesInFHIRPath; + } + + protected boolean rule(List errors, IssueType type, String path, boolean b, String msg) { + String rn = path.contains(".") ? path.substring(0, path.indexOf(".")) : path; + return super.ruleHtml(errors, NO_RULE_DATE, type, path, b, msg, ""+rn+": "+Utilities.escapeXml(msg)); + } + + public List validate(StructureDefinition profile, boolean forBuild) { + List errors = new ArrayList(); + + // must have a FHIR version- GF#3160 + String s = (profile.getKind() == StructureDefinitionKind.LOGICAL) ? "Logical Models" : "Profiles"; + warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getUrl(), profile.hasFhirVersion(), s+" SHOULD state the FHIR Version on which they are based"); + warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getUrl(), profile.hasVersion(), s+" SHOULD state their own version"); + + // extensions must be defined + for (ElementDefinition ec : profile.getDifferential().getElement()) + checkExtensions(profile, errors, "differential", ec); + rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, profile.getId(), profile.hasSnapshot(), "missing Snapshot at "+profile.getName()+"."+profile.getName()); + for (ElementDefinition ec : profile.getSnapshot().getElement()) + checkExtensions(profile, errors, "snapshot", ec); + + if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, profile.getId(), profile.hasSnapshot(), "A snapshot is required")) { + Hashtable snapshotElements = new Hashtable(); + for (ElementDefinition ed : profile.getSnapshot().getElement()) { + snapshotElements.put(ed.getId(), ed); + for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { + if (forBuild) { + if (!inExemptList(inv.getKey())) { +// if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getId()+"::"+ed.getPath()+"::"+inv.getKey(), inv.hasExpression(), "The invariant has no FHIR Path expression ("+inv.getXpath()+")")) { +// try { +// fpe.check(null, profile.getType(), ed.getPath(), inv.getExpression()); // , inv.hasXpath() && inv.getXpath().startsWith("@value") +// } catch (Exception e) { +// // rule(errors, UNKNOWN_DATE_TIME, IssueType.STRUCTURE, profile.getId()+"::"+ed.getPath()+"::"+inv.getId(), false, e.getMessage()); +// } +// } + } + } + } + } + if (snapshotElements != null) { + for (ElementDefinition diffElement : profile.getDifferential().getElement()) { + if (diffElement == null) + throw new Error("Diff Element is null - this is not an expected thing"); + ElementDefinition snapElement = snapshotElements.get(diffElement.getId()); + if (snapElement!=null) { // Happens with profiles in the main build - should be able to fix once snapshot generation is fixed - Lloyd + // We don't care about mustSupport for the root element, if the element is just declaring slicing, if there's a pattern or fixed value, or if the element is being prohibited. + boolean noMustSupport = !checkMustSupport || !snapElement.getPath().contains(".") || snapElement.hasSlicing() || snapElement.hasPattern() || snapElement.hasFixed() || snapElement.getMax().equals("0") || snapElement.hasMustSupport(); + warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, diffElement.getId(), noMustSupport, "Elements included in the differential that aren't prohibited and don't have fixed values or patterns should declare mustSupport: " + snapElement.getPath()); + if (checkAggregation) { + for (TypeRefComponent type : snapElement.getType()) { + if ("http://hl7.org/fhir/Reference".equals(type.getWorkingCode()) || "http://hl7.org/fhir/canonical".equals(type.getWorkingCode())) { + warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, diffElement.getId(), type.hasAggregation(), "Elements with type Reference or canonical should declare aggregation"); + } + } + } + } + } + } + } + return errors; + } + + // these are special cases + private boolean inExemptList(String key) { + return key.startsWith("txt-"); + } + + private boolean checkExtensions(StructureDefinition profile, List errors, String kind, ElementDefinition ec) { + if (!ec.getType().isEmpty() && "Extension".equals(ec.getType().get(0).getWorkingCode()) && ec.getType().get(0).hasProfile()) { + String url = ec.getType().get(0).getProfile().get(0).getValue(); + StructureDefinition defn = context.fetchResource(StructureDefinition.class, url); + if (defn == null) { + defn = getXverExt(profile, errors, url); + } + return rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getId(), defn != null, "Unable to find Extension '"+url+"' referenced at "+profile.getUrl()+" "+kind+" "+ec.getPath()+" ("+ec.getSliceName()+")"); + } else { + return true; + } + } + + } \ No newline at end of file