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