001package org.hl7.fhir.common.hapi.validation.support;
002
003import ca.uhn.fhir.context.FhirContext;
004import ca.uhn.fhir.context.support.ConceptValidationOptions;
005import ca.uhn.fhir.context.support.ValidationSupportContext;
006import org.hl7.fhir.instance.model.api.IBaseResource;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import javax.annotation.Nonnull;
011import javax.annotation.Nullable;
012
013/**
014 * This validation support module may be placed at the end of a {@link ValidationSupportChain}
015 * in order to configure the validator to generate a warning if a resource being validated
016 * contains an unknown code system.
017 *
018 * Note that this module must also be activated by calling {@link #setAllowNonExistentCodeSystem(boolean)}
019 * in order to specify that unknown code systems should be allowed.
020 */
021public class UnknownCodeSystemWarningValidationSupport extends BaseValidationSupport {
022        private static final Logger ourLog = LoggerFactory.getLogger(UnknownCodeSystemWarningValidationSupport.class);
023
024        public static final IssueSeverity DEFAULT_SEVERITY = IssueSeverity.ERROR;
025
026        private IssueSeverity myNonExistentCodeSystemSeverity = DEFAULT_SEVERITY;
027
028        /**
029         * Constructor
030         */
031        public UnknownCodeSystemWarningValidationSupport(FhirContext theFhirContext) {
032                super(theFhirContext);
033        }
034
035        @Override
036        public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
037                return true;
038        }
039
040        @Override
041        public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
042                return canValidateCodeSystem(theValidationSupportContext, theSystem);
043        }
044
045        @Override
046        public CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
047                // filters out error/fatal
048                // NB: this is a secondary check. isCodeSystemSupported
049                // should prevent this from ever calling validate code here
050                // ... but should it ever get called, we'll return null
051                if (!canValidateCodeSystem(theValidationSupportContext, theCodeSystem)) {
052                        return null;
053                }
054
055                CodeValidationResult result = new CodeValidationResult()
056                        .setSeverity(myNonExistentCodeSystemSeverity); // will be warning or info (error/fatal filtered out above)
057
058                result.setMessage("No issues detected during validation");
059
060                switch (myNonExistentCodeSystemSeverity) {
061                        case INFORMATION:
062                                // for warnings, we don't set the code
063                                // cause if we do, the severity is stripped out
064                                // (see VersionSpecificWorkerContextWrapper.convertValidationResult)
065                                result.setCode(theCode);
066                                break;
067                }
068
069                return result;
070        }
071
072        @Nullable
073        @Override
074        public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
075                if (!canValidateCodeSystem(theValidationSupportContext, theCodeSystem)) {
076                        return null;
077                }
078
079                return new CodeValidationResult()
080                        .setCode(theCode)
081                        .setSeverity(IssueSeverity.INFORMATION)
082                        .setMessage("Code " + theCodeSystem + "#" + theCode + " was not checked because the CodeSystem is not available");
083        }
084
085        /**
086         * Returns true if non existent code systems will still validate.
087         * False if they will throw errors.
088         * @return
089         */
090        private boolean allowNonExistentCodeSystems() {
091                switch (myNonExistentCodeSystemSeverity) {
092                        case ERROR:
093                        case FATAL:
094                                return false;
095                        case WARNING:
096                        case INFORMATION:
097                                return true;
098                        default:
099                                ourLog.info("Unknown issue severity " + myNonExistentCodeSystemSeverity.name()
100                                        + ". Treating as INFO/WARNING");
101                                return true;
102                }
103        }
104
105        /**
106         * Determines if the code system can (and should) be validated.
107         * @param theValidationSupportContext
108         * @param theCodeSystem
109         * @return
110         */
111        private boolean canValidateCodeSystem(ValidationSupportContext theValidationSupportContext,
112                                                                                                          String theCodeSystem) {
113                if (!allowNonExistentCodeSystems()) {
114                        return false;
115                }
116                if (theCodeSystem == null) {
117                        return false;
118                }
119                IBaseResource codeSystem = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theCodeSystem);
120                if (codeSystem != null) {
121                        return false;
122                }
123                return true;
124        }
125
126        /**
127         * If set to allow, code system violations will be flagged with Warning by default.
128         * Use setNonExistentCodeSystemSeverity instead.
129         *
130         * @param theAllowNonExistentCodeSystem
131         */
132        @Deprecated
133        public void setAllowNonExistentCodeSystem(boolean theAllowNonExistentCodeSystem) {
134                if (theAllowNonExistentCodeSystem) {
135                        setNonExistentCodeSystemSeverity(IssueSeverity.WARNING);
136                } else {
137                        setNonExistentCodeSystemSeverity(IssueSeverity.ERROR);
138                }
139        }
140
141        /**
142         * Sets the non-existent code system severity.
143         * @param theSeverity
144         */
145        public void setNonExistentCodeSystemSeverity(IssueSeverity theSeverity) {
146                myNonExistentCodeSystemSeverity = theSeverity;
147        }
148}