From 56965820ea378447dbfe21e862123760ae8b4aec Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sun, 13 Oct 2024 22:40:17 +0800 Subject: [PATCH] Add support for -advisor-file, and -clear-tx-cache --- .../utils/validation/IResourceValidator.java | 2 +- .../validation/IValidationPolicyAdvisor.java | 16 ++ .../hl7/fhir/validation/BaseValidator.java | 185 ++++++++---------- .../hl7/fhir/validation/ValidationEngine.java | 10 + .../org/hl7/fhir/validation/ValidatorCli.java | 3 + .../fhir/validation/cli/model/CliContext.java | 39 +++- .../services/StandAloneValidatorFetcher.java | 61 +++++- .../cli/services/ValidationService.java | 28 +++ .../hl7/fhir/validation/cli/utils/Params.java | 15 ++ .../instance/InstanceValidator.java | 16 -- .../BasePolicyAdvisorForFullValidation.java | 10 + .../advisor/JsonDrivenPolicyAdvisor.java | 65 ++---- .../advisor/RulesDrivenPolicyAdvisor.java | 176 +++++++++++++++++ .../advisor/TextDrivenPolicyAdvisor.java | 54 +++++ .../src/main/resources/help/advisor.txt | 2 +- .../validation/tests/ValidationTests.java | 10 + pom.xml | 2 +- 17 files changed, 521 insertions(+), 173 deletions(-) create mode 100644 org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/RulesDrivenPolicyAdvisor.java create mode 100644 org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/TextDrivenPolicyAdvisor.java diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IResourceValidator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IResourceValidator.java index 691c4fa73..4a938b2fe 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IResourceValidator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IResourceValidator.java @@ -80,7 +80,7 @@ public interface IResourceValidator { IResourceValidator setFetcher(IValidatorResourceFetcher value); IValidationPolicyAdvisor getPolicyAdvisor(); - IResourceValidator setPolicyAdvisor(IValidationPolicyAdvisor advisor); + void setPolicyAdvisor(IValidationPolicyAdvisor advisor); IValidationProfileUsageTracker getTracker(); IResourceValidator setTracker(IValidationProfileUsageTracker value); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IValidationPolicyAdvisor.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IValidationPolicyAdvisor.java index 5af265edc..c87508dcd 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IValidationPolicyAdvisor.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/IValidationPolicyAdvisor.java @@ -16,6 +16,21 @@ import org.hl7.fhir.r5.utils.validation.constants.BindingKind; public interface IValidationPolicyAdvisor { + /** + * Internal use, for chaining advisors + * + * @return + */ + ReferenceValidationPolicy getReferencePolicy(); + + /** + * + * @param path - the current path of the element + * @param messageId - the message id (from messages.properties) + * @return true if the validator should ignore the message + */ + boolean suppressMessageId(String path, String messageId); + /** * * @param validator @@ -169,5 +184,6 @@ public interface IValidationPolicyAdvisor { boolean valid, IMessagingServices msgServices, List messages); + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java index ab76c2d1f..cb6f23fdd 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java @@ -69,9 +69,11 @@ import org.hl7.fhir.r5.utils.XVerExtensionManager; import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus; import org.hl7.fhir.r5.utils.validation.IMessagingServices; import org.hl7.fhir.r5.utils.validation.IResourceValidator; +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.IValidationContextResourceLoader; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; +import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.FhirPublication; import org.hl7.fhir.utilities.StandardsStatus; @@ -85,6 +87,7 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; import org.hl7.fhir.utilities.validation.ValidationMessage.Source; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.validation.cli.utils.ValidationLevel; +import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation; import org.hl7.fhir.validation.instance.utils.IndexedElement; import org.hl7.fhir.validation.instance.utils.NodeStack; @@ -165,25 +168,31 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi protected final String BUNDLE = "Bundle"; protected final String LAST_UPDATED = "lastUpdated"; + protected String sessionId; protected BaseValidator parent; - protected Source source; protected IWorkerContext context; protected ValidationTimeTracker timeTracker = new ValidationTimeTracker(); protected XVerExtensionManager xverManager; - protected List trackedMessages = new ArrayList<>(); - protected List messagesToRemove = new ArrayList<>(); - protected ValidationLevel level = ValidationLevel.HINTS; - protected Coding jurisdiction; - protected boolean allowExamples; - protected boolean forPublication; - protected boolean debug; - protected boolean warnOnDraftOrExperimental; - protected Set statusWarnings = new HashSet<>(); - protected BestPracticeWarningLevel bpWarnings = BestPracticeWarningLevel.Warning; - protected String sessionId = Utilities.makeUuidLC(); - protected List usageContexts = new ArrayList(); - protected ValidationOptions baseOptions = new ValidationOptions(FhirPublication.R5); protected IValidatorResourceFetcher fetcher; + protected IValidationPolicyAdvisor policyAdvisor; + + // these two related to removing warnings on extensible bindings in structures that have derivatives that replace their bindings + protected List trackedMessages = new ArrayList<>(); + protected List messagesToRemove = new ArrayList<>(); + + // don't repeatedly raise the same warnings all the time + protected Set statusWarnings = new HashSet<>(); + + protected Source source; // @configuration + protected ValidationLevel level = ValidationLevel.HINTS; // @configuration + protected Coding jurisdiction; // @configuration + protected boolean allowExamples; // @configuration + protected boolean forPublication; // @configuration + protected boolean debug; // @configuration + protected boolean warnOnDraftOrExperimental; // @configuration + protected BestPracticeWarningLevel bpWarnings = BestPracticeWarningLevel.Warning; // @configuration + protected List usageContexts = new ArrayList(); // @configuration + protected ValidationOptions baseOptions = new ValidationOptions(FhirPublication.R5); // @configuration public BaseValidator(IWorkerContext context, XVerExtensionManager xverManager, boolean debug) { super(); @@ -193,6 +202,8 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi this.xverManager = new XVerExtensionManager(context); } this.debug = debug; + sessionId = Utilities.makeUuidLC(); + policyAdvisor = new BasePolicyAdvisorForFullValidation(ReferenceValidationPolicy.CHECK_VALID); urlRegex = Constants.URI_REGEX_XVER.replace("$$", CommaSeparatedStringBuilder.join("|", context.getResourceNames())); } @@ -217,6 +228,8 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi this.usageContexts.addAll(parent.usageContexts); this.baseOptions = parent.baseOptions; this.fetcher = parent.fetcher; + this.sessionId = parent.sessionId; + this.policyAdvisor = parent.policyAdvisor; } private boolean doingLevel(IssueSeverity error) { @@ -261,75 +274,22 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi protected String urlRegex; - /** - * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails - * - * @param thePass - * Set this parameter to false if the validation does not pass - * @return Returns thePass (in other words, returns true if the rule did not fail validation) - */ - @Deprecated - protected boolean fail(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg) { - if (!thePass && doingErrors()) { - addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.FATAL, null); + private boolean suppressMsg(String path, String theMessage) { + if (policyAdvisor == null) { + return false; + } else { + return policyAdvisor.suppressMessageId(path, theMessage); } - return thePass; } protected boolean fail(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String msg = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.FATAL, theMessage); } return thePass; } - /** - * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails - * - * @param thePass - * Set this parameter to false if the validation does not pass - * @return Returns thePass (in other words, returns true if the rule did not fail validation) - */ - @Deprecated - protected boolean fail(List errors, String ruleDate, IssueType type, List pathParts, boolean thePass, String msg) { - if (!thePass && doingErrors()) { - String path = toPath(pathParts); - addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.FATAL, null); - } - return thePass; - } - - /** - * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails - * - * @param thePass - * Set this parameter to false if the validation does not pass - * @return Returns thePass (in other words, returns true if the rule did not fail validation) - */ - @Deprecated - protected boolean fail(List errors, String ruleDate, IssueType type, List pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { - String path = toPath(pathParts); - addValidationMessage(errors, ruleDate, type, -1, -1, path, context.formatMessage(theMessage, theMessageArguments), IssueSeverity.FATAL, theMessage); - } - return thePass; - } - - /** - * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails - * - * @param thePass - * Set this parameter to false if the validation does not pass - * @return Returns thePass (in other words, returns true if the rule did not fail validation) - */ - @Deprecated - protected boolean fail(List errors, String ruleDate, IssueType type, String path, boolean thePass, String msg) { - if (!thePass && doingErrors()) { - addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.FATAL, null); - } - return thePass; - } //TODO: i18n protected boolean grammarWord(String w) { return w.equals("and") || w.equals("or") || w.equals("a") || w.equals("the") || w.equals("for") || w.equals("this") || w.equals("that") || w.equals("of"); @@ -343,7 +303,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean hint(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(path, msg)) { String message = context.formatMessage(msg); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, msg); } @@ -358,7 +318,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean hintInv(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, String invId) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(path, invId)) { String message = context.formatMessage(msg); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, msg).setInvId(invId); } @@ -397,7 +357,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean hint(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, theMessage); } @@ -405,7 +365,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean hintPlural(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String theMessage, Object... theMessageArguments) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(path, theMessage)) { String message = context.formatMessagePlural(num, theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, theMessage); } @@ -418,7 +378,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean txHint(List errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, Source.TerminologyEngine, theMessage).setTxLink(txLink); } @@ -433,7 +393,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean hint(List errors, String ruleDate, IssueType type, List pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(CommaSeparatedStringBuilder.join(".", pathParts), theMessage)) { String path = toPath(pathParts); String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.INFORMATION, theMessage); @@ -449,7 +409,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean hint(List errors, String ruleDate, IssueType type, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingHints()) { + if (!thePass && doingHints() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.INFORMATION, null); } @@ -464,7 +424,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean rule(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.ERROR, theMessage); } @@ -472,7 +432,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean ruleInv(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, String invId, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.ERROR, invId).setInvId(invId); } @@ -480,7 +440,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean rule(List errors, String ruleDate, IssueType type, NodeStack stack, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(stack.getLiteralPath(), theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, stack.line(), stack.col(), stack.getLiteralPath(), message, IssueSeverity.ERROR, theMessage); } @@ -492,7 +452,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean rulePlural(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String message = context.formatMessagePlural(num, theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.ERROR, theMessage); } @@ -500,7 +460,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean txRule(List errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); ValidationMessage vm = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, message, IssueSeverity.ERROR).setMessageId(idForMessage(theMessage, message)); vm.setRuleDate(ruleDate); @@ -538,7 +498,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean rule(List errors, String ruleDate, IssueType type, List pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(CommaSeparatedStringBuilder.join(".", pathParts), theMessage)) { String path = toPath(pathParts); String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.ERROR, theMessage); @@ -556,7 +516,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi protected boolean rule(List errors, String ruleDate, IssueType type, String path, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.ERROR, theMessage); } @@ -564,7 +524,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean rulePlural(List errors, String ruleDate, IssueType type, String path, boolean thePass, int num, String theMessage, Object... theMessageArguments) { - if (!thePass && doingErrors()) { + if (!thePass && doingErrors() && !suppressMsg(path, theMessage)) { String message = context.formatMessagePlural(num, theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.ERROR, theMessage); } @@ -630,7 +590,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean warning(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); IssueSeverity severity = IssueSeverity.WARNING; addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, msg); @@ -640,7 +600,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean warning(List errors, String ruleDate, IssueType type, int line, int col, String path, String id, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); IssueSeverity severity = IssueSeverity.WARNING; addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, id); @@ -650,7 +610,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean warningInv(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, String invId, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, invId)) { String nmsg = context.formatMessage(msg, theMessageArguments); IssueSeverity severity = IssueSeverity.WARNING; String id = idForMessage(msg, nmsg); @@ -669,7 +629,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean warningPlural(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessagePlural(num, msg, theMessageArguments); IssueSeverity severity = IssueSeverity.WARNING; addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, msg); @@ -711,7 +671,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean txWarning(List errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(idForMessage(msg, nmsg)); vmsg.setRuleDate(ruleDate); @@ -744,7 +704,9 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi vmsg.setMessageId(issue.getExtensionString(ToolingExtensions.EXT_ISSUE_MSG_ID)); } - errors.add(vmsg); + if (!suppressMsg(path, vmsg.getMessageId())) { + errors.add(vmsg); + } return vmsg; } @@ -760,7 +722,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean txWarningForLaterRemoval(Object location, List errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg); vmsg.setRuleDate(ruleDate); @@ -789,7 +751,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean warningOrError(boolean isError, List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass) { + if (!thePass && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); IssueSeverity lvl = isError ? IssueSeverity.ERROR : IssueSeverity.WARNING; if (doingLevel(lvl)) { @@ -805,7 +767,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi } protected boolean hintOrError(boolean isError, List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass) { + if (!thePass && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); IssueSeverity lvl = isError ? IssueSeverity.ERROR : IssueSeverity.INFORMATION; if (doingLevel(lvl)) { @@ -824,7 +786,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean warning(List errors, String ruleDate, IssueType type, List pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(CommaSeparatedStringBuilder.join(".", pathParts), theMessage)) { String path = toPath(pathParts); String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.WARNING, theMessage); @@ -840,7 +802,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean warning(List errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String message = context.formatMessage(msg, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.WARNING, null); } @@ -855,7 +817,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean warningOrHint(List errors, String ruleDate, IssueType type, String path, boolean thePass, boolean warning, String msg, Object... theMessageArguments) { - if (!thePass) { + if (!thePass && !suppressMsg(path, msg)) { String message = context.formatMessage(msg, theMessageArguments); IssueSeverity lvl = warning ? IssueSeverity.WARNING : IssueSeverity.INFORMATION; if (doingLevel(lvl)) { @@ -873,7 +835,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean warningHtml(List errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { addValidationMessage(errors, ruleDate, type, path, msg, html, IssueSeverity.WARNING, null); } return thePass; @@ -887,7 +849,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean warningHtml(List errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); addValidationMessage(errors, ruleDate, type, path, nmsg, html, IssueSeverity.WARNING, msg); } @@ -903,7 +865,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean suppressedwarning(List errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, IssueSeverity.INFORMATION, msg); } @@ -919,7 +881,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean suppressedwarning(List errors, String ruleDate, IssueType type, List pathParts, boolean thePass, String theMessage, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(CommaSeparatedStringBuilder.join(".", pathParts), theMessage)) { String path = toPath(pathParts); String message = context.formatMessage(theMessage, theMessageArguments); addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.INFORMATION, theMessage); @@ -974,7 +936,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ protected boolean suppressedwarning(List errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html, Object... theMessageArguments) { - if (!thePass && doingWarnings()) { + if (!thePass && doingWarnings() && !suppressMsg(path, msg)) { String nmsg = context.formatMessage(msg, theMessageArguments); addValidationMessage(errors, ruleDate, type, path, nmsg, html, IssueSeverity.INFORMATION, msg); } @@ -1681,4 +1643,17 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi return false; } + public IValidationPolicyAdvisor getPolicyAdvisor() { + return policyAdvisor; + } + + public void setPolicyAdvisor(IValidationPolicyAdvisor advisor) { + if (advisor == null) { + throw new Error("Cannot set advisor to null"); + } + this.policyAdvisor = advisor; + } + + + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 72808e09c..545670b98 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -1288,4 +1288,14 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP definition, structure, resource, valid, msgServices, messages); } + @Override + public boolean suppressMessageId(String path, String messageId) { + return policyAdvisor.suppressMessageId(path, messageId); + } + + @Override + public ReferenceValidationPolicy getReferencePolicy() { + return ReferenceValidationPolicy.IGNORE; + } + } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java index 2e8631384..86b461fbe 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java @@ -376,6 +376,9 @@ public class ValidatorCli { ((StandaloneTask) cliTask).executeTask(cliContext,params,tt,tts); } + if (cliContext.getAdvisorFile() != null) { + System.out.println("Note: Some validation issues might be hidden by the advisor settings in the file "+cliContext.getAdvisorFile()); + } System.out.println("Done. " + tt.report()+". Max Memory = "+Utilities.describeSize(Runtime.getRuntime().maxMemory())); SystemExitManager.finish(); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java index 6d5e0cb4a..37a4b9c5a 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java @@ -132,6 +132,12 @@ public class CliContext { @JsonProperty("showTimes") private boolean showTimes = false; + @JsonProperty("showTerminologyRouting") + private boolean showTerminologyRouting = false; + + @JsonProperty("clearTxCache") + private boolean clearTxCache = false; + @JsonProperty("locale") private String locale = Locale.ENGLISH.toLanguageTag(); @@ -172,6 +178,9 @@ public class CliContext { @JsonProperty("noExperimentalContent") private boolean noExperimentalContent; + @JsonProperty("advisorFile") + private String advisorFile; + @JsonProperty("baseEngine") public String getBaseEngine() { return baseEngine; @@ -754,6 +763,22 @@ public class CliContext { this.showTimes = showTimes; } + public boolean isShowTerminologyRouting() { + return showTerminologyRouting; + } + + public void setShowTerminologyRouting(boolean showTerminologyRouting) { + this.showTerminologyRouting = showTerminologyRouting; + } + + public boolean isClearTxCache() { + return clearTxCache; + } + + public void setClearTxCache(boolean clearTxCache) { + this.clearTxCache = clearTxCache; + } + public String getOutputStyle() { return outputStyle; } @@ -852,6 +877,7 @@ public class CliContext { Objects.equals(watchScanDelay, that.watchScanDelay) && Objects.equals(unknownCodeSystemsCauseErrors, that.unknownCodeSystemsCauseErrors) && Objects.equals(noExperimentalContent, that.noExperimentalContent) && + Objects.equals(advisorFile, that.advisorFile) && Objects.equals(watchSettleTime, that.watchSettleTime) ; } @@ -860,7 +886,7 @@ public class CliContext { return Objects.hash(baseEngine, doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching, noExtensibleBindingMessages, noInvariants, displayWarnings, wantInvariantsInMessages, map, output, outputSuffix, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, srcLang, tgtLang, fhirpath, snomedCT, targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, showMessageIds, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars, - watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel, unknownCodeSystemsCauseErrors, noExperimentalContent, htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes); + watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel, unknownCodeSystemsCauseErrors, noExperimentalContent, advisorFile, htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes); } @Override @@ -922,6 +948,7 @@ public class CliContext { ", watchScanDelay=" + watchScanDelay + ", unknownCodeSystemsCauseErrors=" + unknownCodeSystemsCauseErrors + ", noExperimentalContent=" + noExperimentalContent + + ", advisorFile=" + advisorFile + '}'; } @@ -1002,5 +1029,15 @@ public class CliContext { this.noExperimentalContent = noExperimentalContent; } + @JsonProperty("advisorFile") + public String getAdvisorFile() { + return advisorFile; + } + + @JsonProperty("advisorFile") + public void setAdvisorFile(String advisorFile) { + this.advisorFile = advisorFile; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java index a239fd2d4..20beab9a8 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java @@ -17,12 +17,14 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.context.IWorkerContextManager; import org.hl7.fhir.r5.elementmodel.Element; +import org.hl7.fhir.r5.elementmodel.Element.SpecialElement; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.terminologies.client.ITerminologyClient; +import org.hl7.fhir.r5.utils.validation.IMessagingServices; import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; @@ -41,13 +43,14 @@ import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.json.parser.JsonParser; import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; import org.hl7.fhir.utilities.npm.NpmPackage; +import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.validation.cli.utils.Common; import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation; import javax.annotation.Nonnull; -public class StandAloneValidatorFetcher extends BasePolicyAdvisorForFullValidation implements IValidatorResourceFetcher, IValidationPolicyAdvisor, IWorkerContextManager.ICanonicalResourceLocator { +public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IValidationPolicyAdvisor, IWorkerContextManager.ICanonicalResourceLocator { List mappingsUris = new ArrayList<>(); private FilesystemPackageCacheManager pcm; @@ -56,12 +59,13 @@ public class StandAloneValidatorFetcher extends BasePolicyAdvisorForFullValidati private Map urlList = new HashMap<>(); private Map pidList = new HashMap<>(); private Map pidMap = new HashMap<>(); - + private IValidationPolicyAdvisor policyAdvisor; + public StandAloneValidatorFetcher(FilesystemPackageCacheManager pcm, IWorkerContext context, IPackageInstaller installer) { - super(ReferenceValidationPolicy.IGNORE); this.pcm = pcm; this.context = context; this.installer = installer; + this.policyAdvisor = new BasePolicyAdvisorForFullValidation(ReferenceValidationPolicy.IGNORE); } @Override @@ -297,4 +301,55 @@ public class StandAloneValidatorFetcher extends BasePolicyAdvisorForFullValidati return new HashSet<>(); } + @Override + public boolean suppressMessageId(String path, String messageId) { + return policyAdvisor.suppressMessageId(path, messageId); + } + + @Override + public ContainedReferenceValidationPolicy policyForContained(IResourceValidator validator, Object appContext, + StructureDefinition structure, ElementDefinition element, String containerType, String containerId, + SpecialElement containingResourceType, String path, String url) { + return policyAdvisor.policyForContained(validator, appContext, structure, element, containerType, containerId, containingResourceType, path, url); + } + + @Override + public EnumSet policyForResource(IResourceValidator validator, Object appContext, + StructureDefinition type, String path) { + return policyAdvisor.policyForResource(validator, appContext, type, path); + } + + @Override + public EnumSet policyForElement(IResourceValidator validator, Object appContext, + StructureDefinition structure, ElementDefinition element, String path) { + return policyAdvisor.policyForElement(validator, appContext, structure, element, path); + } + + @Override + public EnumSet policyForCodedContent(IResourceValidator validator, Object appContext, + String stackPath, ElementDefinition definition, StructureDefinition structure, BindingKind kind, + AdditionalBindingPurpose purpose, ValueSet valueSet, List systems) { + return policyAdvisor.policyForCodedContent(validator, appContext, stackPath, definition, structure, kind, purpose, valueSet, systems); + } + + @Override + public List getImpliedProfilesForResource(IResourceValidator validator, Object appContext, + String stackPath, ElementDefinition definition, StructureDefinition structure, Element resource, boolean valid, + IMessagingServices msgServices, List messages) { + return policyAdvisor.getImpliedProfilesForResource(validator, appContext, stackPath, definition, structure, resource, valid, msgServices, messages); + } + + @Override + public ReferenceValidationPolicy getReferencePolicy() { + return policyAdvisor.getReferencePolicy(); + } + + public IValidationPolicyAdvisor getPolicyAdvisor() { + return policyAdvisor; + } + + public void setPolicyAdvisor(IValidationPolicyAdvisor policyAdvisor) { + this.policyAdvisor = policyAdvisor; + } + } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java index 644926361..b06bcdd55 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java @@ -40,6 +40,7 @@ import org.hl7.fhir.r5.renderers.spreadsheets.ConceptMapSpreadsheetGenerator; import org.hl7.fhir.r5.renderers.spreadsheets.StructureDefinitionSpreadsheetGenerator; import org.hl7.fhir.r5.renderers.spreadsheets.ValueSetSpreadsheetGenerator; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; +import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager.InternalLogEvent; import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache; import org.hl7.fhir.utilities.FhirPublication; import org.hl7.fhir.utilities.SystemExitManager; @@ -69,6 +70,8 @@ import org.hl7.fhir.validation.cli.renderers.ValidationOutputRenderer; import org.hl7.fhir.validation.cli.utils.Common; import org.hl7.fhir.validation.cli.utils.EngineMode; import org.hl7.fhir.validation.cli.utils.VersionSourceInformation; +import org.hl7.fhir.validation.instance.advisor.JsonDrivenPolicyAdvisor; +import org.hl7.fhir.validation.instance.advisor.TextDrivenPolicyAdvisor; public class ValidationService { @@ -155,6 +158,7 @@ public class ValidationService { if (request.getCliContext().isShowTimes()) { response.getValidationTimes().put(fileToValidate.getFileName(), validatedFragments.getValidationTime()); } + } } @@ -289,6 +293,19 @@ public class ValidationService { TextFile.stringToFile(html, cliContext.getHtmlOutput()); System.out.println("HTML Summary in " + cliContext.getHtmlOutput()); } + + if (cliContext.isShowTerminologyRouting()) { + System.out.println(""); + System.out.println("Terminology Routing Dump ---------------------------------------"); + if (validator.getContext().getTxClientManager().getInternalLog().isEmpty()) { + System.out.println("(nothing happened)"); + } else { + for (InternalLogEvent log : validator.getContext().getTxClientManager().getInternalLog()) { + System.out.println(log.getMessage()+" -> "+log.getServer()+" (for VS "+log.getVs()+" with systems '"+log.getSystems()+"', choices = '"+log.getChoices()+"')"); + } + } + validator.getContext().getTxClientManager().getInternalLog().clear(); + } } if (watch != ValidatorWatchMode.NONE) { if (statusNeeded) { @@ -545,6 +562,10 @@ public class ValidationService { System.out.println(" No Terminology Cache"); } else { System.out.println(" Terminology Cache at "+validationEngine.getContext().getTxCache().getFolder()); + if (cliContext.isClearTxCache()) { + System.out.println(" Terminology Cache Entries Cleaned out"); + validationEngine.getContext().getTxCache().clear(); + } } System.out.print(" Get set... "); validationEngine.setQuestionnaireMode(cliContext.getQuestionnaireMode()); @@ -584,6 +605,13 @@ public class ValidationService { validationEngine.setFetcher(fetcher); validationEngine.getContext().setLocator(fetcher); validationEngine.setPolicyAdvisor(fetcher); + if (cliContext.getAdvisorFile() != null) { + if (cliContext.getAdvisorFile().endsWith(".json")) { + fetcher.setPolicyAdvisor(new JsonDrivenPolicyAdvisor(fetcher.getPolicyAdvisor(), new File(cliContext.getAdvisorFile()))); + } else { + fetcher.setPolicyAdvisor(new TextDrivenPolicyAdvisor(fetcher.getPolicyAdvisor(), new File(cliContext.getAdvisorFile()))); + } + } } validationEngine.getBundleValidationRules().addAll(cliContext.getBundleValidationRules()); validationEngine.setJurisdiction(CodeSystemUtilities.readCoding(cliContext.getJurisdiction())); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java index 8f646e86b..675acd332 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java @@ -54,6 +54,8 @@ public class Params { public static final String TERMINOLOGY = "-tx"; public static final String TERMINOLOGY_LOG = "-txLog"; public static final String TERMINOLOGY_CACHE = "-txCache"; + public static final String TERMINOLOGY_ROUTING = "-tx-routing"; + public static final String TERMINOLOGY_CACHE_CLEAR = "-clear-tx-cache"; public static final String LOG = "-log"; public static final String LANGUAGE = "-language"; public static final String IMPLEMENTATION_GUIDE = "-ig"; @@ -86,6 +88,7 @@ public class Params { public static final String SHOW_TIMES = "-show-times"; public static final String ALLOW_EXAMPLE_URLS = "-allow-example-urls"; public static final String OUTPUT_STYLE = "-output-style"; + public static final String ADVSIOR_FILE = "-advisor-file"; public static final String DO_IMPLICIT_FHIRPATH_STRING_CONVERSION = "-implicit-fhirpath-string-conversions"; public static final String JURISDICTION = "-jurisdiction"; public static final String HTML_IN_MARKDOWN = "-html-in-markdown"; @@ -339,10 +342,22 @@ public class Params { } else { throw new Error("Value for "+ALLOW_EXAMPLE_URLS+" not understood: "+bl); } + } else if (args[i].equals(TERMINOLOGY_ROUTING)) { + cliContext.setShowTerminologyRouting(true); + } else if (args[i].equals(TERMINOLOGY_CACHE_CLEAR)) { + cliContext.setClearTxCache(true); } else if (args[i].equals(SHOW_TIMES)) { cliContext.setShowTimes(true); } else if (args[i].equals(OUTPUT_STYLE)) { cliContext.setOutputStyle(args[++i]); + } else if (args[i].equals(ADVSIOR_FILE)) { + cliContext.setAdvisorFile(args[++i]); + File f = new File(cliContext.getAdvisorFile()); + if (!f.exists()) { + throw new Error("Cannot find advisor file "+cliContext.getAdvisorFile()); + } else if (!Utilities.existsInList(Utilities.getFileExtension(f.getName()), "json", "txt")) { + throw new Error("Advisor file "+cliContext.getAdvisorFile()+" must be a .json or a .txt file"); + } } else if (args[i].equals(SCAN)) { cliContext.setMode(EngineMode.SCAN); } else if (args[i].equals(TERMINOLOGY)) { 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 2d511bf6a..0028218ed 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 @@ -574,7 +574,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean noBindingMsgSuppressed; private Map fetchCache = new HashMap<>(); private HashMap resourceTracker = new HashMap<>(); - private IValidationPolicyAdvisor policyAdvisor = new BasePolicyAdvisorForFullValidation(ReferenceValidationPolicy.CHECK_VALID); long time = 0; long start = 0; long lastlog = 0; @@ -664,21 +663,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return this; } - - @Override - public IValidationPolicyAdvisor getPolicyAdvisor() { - return policyAdvisor; - } - - @Override - public IResourceValidator setPolicyAdvisor(IValidationPolicyAdvisor advisor) { - if (advisor == null) { - throw new Error("Cannot set advisor to null"); - } - this.policyAdvisor = advisor; - return this; - } - public IValidationProfileUsageTracker getTracker() { return this.tracker; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/BasePolicyAdvisorForFullValidation.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/BasePolicyAdvisorForFullValidation.java index 4fd00c536..79b63eecb 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/BasePolicyAdvisorForFullValidation.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/BasePolicyAdvisorForFullValidation.java @@ -169,5 +169,15 @@ public class BasePolicyAdvisorForFullValidation implements IValidationPolicyAdvi return false; } + @Override + public boolean suppressMessageId(String path, String messageId) { + return false; + } + + @Override + public ReferenceValidationPolicy getReferencePolicy() { + return refpol; + } + } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/JsonDrivenPolicyAdvisor.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/JsonDrivenPolicyAdvisor.java index abaed6596..8121f9d62 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/JsonDrivenPolicyAdvisor.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/JsonDrivenPolicyAdvisor.java @@ -18,60 +18,35 @@ import org.hl7.fhir.r5.utils.validation.constants.BindingKind; import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.json.JsonException; +import org.hl7.fhir.utilities.json.model.JsonElement; import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.json.parser.JsonParser; import org.hl7.fhir.utilities.validation.ValidationMessage; -public class JsonDrivenPolicyAdvisor implements IValidationPolicyAdvisor { +public class JsonDrivenPolicyAdvisor extends RulesDrivenPolicyAdvisor { - private JsonObject config; - private IValidationPolicyAdvisor base; - - public JsonDrivenPolicyAdvisor(IValidationPolicyAdvisor base, JsonObject config) { - this.base = base; - this.config = config; + public JsonDrivenPolicyAdvisor(IValidationPolicyAdvisor base, File source) throws JsonException, IOException { + super(base); + load(source); } - public JsonDrivenPolicyAdvisor(String config) throws JsonException, IOException { - this.config = JsonParser.parseObject(config, true); + public JsonDrivenPolicyAdvisor(ReferenceValidationPolicy refpol, File source) throws JsonException, IOException { + super(refpol); + load(source); } - public JsonDrivenPolicyAdvisor(File config) throws JsonException, IOException { - this.config = JsonParser.parseObject(new FileInputStream(config), true); - } - - public JsonDrivenPolicyAdvisor(FileInputStream config) throws JsonException, IOException { - this.config = JsonParser.parseObject(config, true); - } - - @Override - public ReferenceValidationPolicy policyForReference(IResourceValidator validator, Object appContext, String path, String url) { - return base.policyForReference(validator, appContext, path, url); - } - - @Override - public ContainedReferenceValidationPolicy policyForContained(IResourceValidator validator, Object appContext, StructureDefinition structure, ElementDefinition element, String containerType, String containerId, SpecialElement containingResourceType, String path, String url) { - return base.policyForContained(validator, appContext, structure, element, containerType, containerId, containingResourceType, path, url); - } - - @Override - public EnumSet policyForResource(IResourceValidator validator, Object appContext, StructureDefinition type, String path) { - return base.policyForResource(validator, appContext, type, path); - } - - @Override - public EnumSet policyForElement(IResourceValidator validator, Object appContext, StructureDefinition structure, ElementDefinition element, String path) { - return base.policyForElement(validator, appContext, structure, element, path); - } - - @Override - public EnumSet policyForCodedContent(IResourceValidator validator, Object appContext, String stackPath, ElementDefinition definition, StructureDefinition structure, BindingKind kind, AdditionalBindingPurpose purpose, ValueSet valueSet, List systems) { - return base.policyForCodedContent(validator, appContext, stackPath, definition, structure, kind, purpose, valueSet, systems); - } - - @Override - public List getImpliedProfilesForResource(IResourceValidator validator, Object appContext, String stackPath, ElementDefinition definition, StructureDefinition structure, Element resource, boolean valid, IMessagingServices msgServices, List messages) { - return base.getImpliedProfilesForResource(validator, appContext, stackPath, definition, structure, resource, valid, msgServices, messages); + private void load(File source) throws JsonException, IOException { + JsonObject json = JsonParser.parseObject(source); + for (JsonElement e : json.forceArray("suppress").getItems()) { + String s = e.asString(); + String id = s; + String path = null; + if (s.contains("@")) { + id = s.substring(0, s.indexOf("@")); + path = s.substring(s.indexOf("@")+1); + } + addSuppressMessageRule(id, path); + } } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/RulesDrivenPolicyAdvisor.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/RulesDrivenPolicyAdvisor.java new file mode 100644 index 000000000..fe5a9f4ad --- /dev/null +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/RulesDrivenPolicyAdvisor.java @@ -0,0 +1,176 @@ +package org.hl7.fhir.validation.instance.advisor; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import org.hl7.fhir.r5.elementmodel.Element; +import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.utils.validation.IMessagingServices; +import org.hl7.fhir.r5.utils.validation.IResourceValidator; +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.AdditionalBindingPurpose; +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.CodedContentValidationAction; +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ElementValidationAction; +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ResourceValidationAction; +import org.hl7.fhir.r5.utils.validation.constants.BindingKind; +import org.hl7.fhir.r5.utils.validation.constants.CodedContentValidationPolicy; +import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; +import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; +import org.hl7.fhir.utilities.validation.ValidationMessage; + +public class RulesDrivenPolicyAdvisor extends BasePolicyAdvisorForFullValidation { + + private IValidationPolicyAdvisor base; + + public RulesDrivenPolicyAdvisor(ReferenceValidationPolicy refpol) { + super(refpol); + base = null; + } + + public RulesDrivenPolicyAdvisor(IValidationPolicyAdvisor base) { + super(base.getReferencePolicy()); + this.base = base; + } + + private class SuppressMessageRule { + private String id; + private String path; + protected SuppressMessageRule(String id, String path) { + super(); + this.id = id; + this.path = path; + } + public String getId() { + return id; + } + public String getPath() { + return path; + } + public boolean matches(String mid, String p) { + if (((id == null) || id.equals(mid)) && ((path == null) || path.equals(p))) { + suppressed++; + return true; + } else if (((id == null) || mid.matches(id)) && ((path == null) || p.matches(path))) { + suppressed++; + return true; + } else { + return false; + } + } + } + + private List suppressMessageRules = new ArrayList<>(); + private int suppressed = 0; + + protected void addSuppressMessageRule(String id, String path) { + suppressMessageRules.add(new SuppressMessageRule(id, path)); + } + + @Override + public boolean suppressMessageId(String path, String messageId) { + for (SuppressMessageRule rule : suppressMessageRules) { + if (rule.matches(messageId, path)) { + return true; + } + } + if (base != null) { + return base.suppressMessageId(path, messageId); + } else { + return super.suppressMessageId(path, messageId); + } + } + + + @Override + public ReferenceValidationPolicy policyForReference(IResourceValidator validator, + Object appContext, + String path, + String url) { + if (base != null) { + return base.policyForReference(validator, appContext, path, url); + } else { + return super.policyForReference(validator, appContext, path, url); + } + } + + @Override + public ContainedReferenceValidationPolicy policyForContained(IResourceValidator validator, + Object appContext, + StructureDefinition structure, + ElementDefinition element, + String containerType, + String containerId, + Element.SpecialElement containingResourceType, + String path, + String url) { + if (base != null) { + return base.policyForContained(validator, appContext, structure, element, containerType, containerId, containingResourceType, path, url); + } else { + return super.policyForContained(validator, appContext, structure, element, containerType, containerId, containingResourceType, path, url); + } + } + + @Override + public EnumSet policyForResource(IResourceValidator validator, + Object appContext, + StructureDefinition type, + String path) { + if (base != null) { + return base.policyForResource(validator, appContext, type, path); + } else { + return super.policyForResource(validator, appContext, type, path); + } + } + + @Override + public EnumSet policyForElement(IResourceValidator validator, + Object appContext, + StructureDefinition structure, + ElementDefinition element, + String path) { + if (base != null) { + return base.policyForElement(validator, appContext, structure, element, path); + } else { + return super.policyForElement(validator, appContext, structure, element, path); + } + } + + @Override + public EnumSet policyForCodedContent(IResourceValidator validator, + Object appContext, + String stackPath, + ElementDefinition definition, + StructureDefinition structure, + BindingKind kind, + AdditionalBindingPurpose purpose, + ValueSet valueSet, + List systems) { + if (base != null) { + return base.policyForCodedContent(validator, appContext, stackPath, definition, structure, kind, purpose, valueSet, systems); + } else { + return super.policyForCodedContent(validator, appContext, stackPath, definition, structure, kind, purpose, valueSet, systems); + } + } + + @Override + public List getImpliedProfilesForResource(IResourceValidator validator, + Object appContext, + String stackPath, + ElementDefinition definition, + StructureDefinition structure, + Element resource, + boolean valid, + IMessagingServices msgServices, + List messages) { + if (base != null) { + return base.getImpliedProfilesForResource(validator, appContext, stackPath, definition, structure, resource, valid, msgServices, messages); + } else { + return super.getImpliedProfilesForResource(validator, appContext, stackPath, definition, structure, resource, valid, msgServices, messages); + } + } + + +} diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/TextDrivenPolicyAdvisor.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/TextDrivenPolicyAdvisor.java new file mode 100644 index 000000000..2920d51ae --- /dev/null +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/advisor/TextDrivenPolicyAdvisor.java @@ -0,0 +1,54 @@ +package org.hl7.fhir.validation.instance.advisor; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; +import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.json.JsonException; + +public class TextDrivenPolicyAdvisor extends RulesDrivenPolicyAdvisor { + + public TextDrivenPolicyAdvisor(IValidationPolicyAdvisor base, File source) throws JsonException, IOException { + super(base); + load(source); + } + + public TextDrivenPolicyAdvisor(ReferenceValidationPolicy refpol, File source) throws JsonException, IOException { + super(refpol); + load(source); + } + + private void load(File source) throws JsonException, IOException { + BufferedReader reader = new BufferedReader(new FileReader(source)); + String line = reader.readLine(); + while (line != null) { + processLine(line); + line = reader.readLine(); + } + reader.close(); + } + + private void processLine(String line) { + line = line.trim(); + if (Utilities.noString(line) || line.startsWith("#")) { + return; + } + if (line.startsWith("-")) { + String s = line.substring(1).trim(); + String id = s; + String path = null; + if (s.contains("@")) { + id = s.substring(0, s.indexOf("@")); + path = s.substring(s.indexOf("@")+1); + } + addSuppressMessageRule(id, path); + } else { + // ignore it for now + } + + } +} diff --git a/org.hl7.fhir.validation/src/main/resources/help/advisor.txt b/org.hl7.fhir.validation/src/main/resources/help/advisor.txt index c86340d44..ac48436f5 100644 --- a/org.hl7.fhir.validation/src/main/resources/help/advisor.txt +++ b/org.hl7.fhir.validation/src/main/resources/help/advisor.txt @@ -34,7 +34,7 @@ Some notes on this approach: * the messageId is directly tied to the format of the message. For some messages, there are multiple variants of the message, each with their own id, and these all need to be suppressed * message ids are stable (including across languages), but sometimes an existing message is improved in some contexts, and new message ids are introduced in later versions * the underlying validation still happens, even when the messages are suppressed - +* for invariants in profiles, the message id is the profile URL and the invariant id e.g. http://hl7.org/fhir/StructureDefinition/DomainResource#dom-6 ## Controlling what validation runs diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java index 929b97395..cb155d739 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java @@ -920,4 +920,14 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe return null; } } + + @Override + public boolean suppressMessageId(String path, String messageId) { + return false; + } + + @Override + public ReferenceValidationPolicy getReferencePolicy() { + return ReferenceValidationPolicy.IGNORE; + } } \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7679b0e5a..267b57fc7 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 2.17.0 32.0.1-jre 6.4.1 - 1.5.26 + 1.5.27-SNAPSHOT 2.17.0 5.9.2 1.8.2