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.LookupCodeRequest;
006import ca.uhn.fhir.context.support.ValidationSupportContext;
007import jakarta.annotation.Nonnull;
008import jakarta.annotation.Nullable;
009import org.apache.commons.lang3.Validate;
010import org.hl7.fhir.instance.model.api.IBaseResource;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
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 String getName() {
038                return getFhirContext().getVersion().getVersion() + " Unknown Code System Warning Validation Support";
039        }
040
041        @Override
042        public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
043                return true;
044        }
045
046        @Override
047        public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
048                return canValidateCodeSystem(theValidationSupportContext, theSystem);
049        }
050
051        @Nullable
052        @Override
053        public LookupCodeResult lookupCode(
054                        ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) {
055                // filters out error/fatal
056                if (canValidateCodeSystem(theValidationSupportContext, theLookupCodeRequest.getSystem())) {
057                        return new LookupCodeResult().setFound(true);
058                }
059
060                return null;
061        }
062
063        @Override
064        public CodeValidationResult validateCode(
065                        @Nonnull ValidationSupportContext theValidationSupportContext,
066                        @Nonnull ConceptValidationOptions theOptions,
067                        String theCodeSystem,
068                        String theCode,
069                        String theDisplay,
070                        String theValueSetUrl) {
071                // filters out error/fatal
072                if (!canValidateCodeSystem(theValidationSupportContext, theCodeSystem)) {
073                        return null;
074                }
075
076                CodeValidationResult result = new CodeValidationResult();
077                // will be warning or info (error/fatal filtered out above)
078                result.setSeverity(myNonExistentCodeSystemSeverity);
079                String theMessage = "CodeSystem is unknown and can't be validated: " + theCodeSystem + " for '" + theCodeSystem
080                                + "#" + theCode + "'";
081                result.setMessage(theMessage);
082
083                // For information level, we just strip out the severity+message entirely
084                // so that nothing appears in the validation result
085                if (myNonExistentCodeSystemSeverity == IssueSeverity.INFORMATION) {
086                        result.setCode(theCode);
087                        result.setSeverity(null);
088                        result.setMessage(null);
089                } else {
090                        result.addCodeValidationIssue(new CodeValidationIssue(
091                                        theMessage,
092                                        myNonExistentCodeSystemSeverity,
093                                        CodeValidationIssueCode.NOT_FOUND,
094                                        CodeValidationIssueCoding.NOT_FOUND));
095                }
096
097                return result;
098        }
099
100        @Nullable
101        @Override
102        public CodeValidationResult validateCodeInValueSet(
103                        ValidationSupportContext theValidationSupportContext,
104                        ConceptValidationOptions theOptions,
105                        String theCodeSystem,
106                        String theCode,
107                        String theDisplay,
108                        @Nonnull IBaseResource theValueSet) {
109                if (!canValidateCodeSystem(theValidationSupportContext, theCodeSystem)) {
110                        return null;
111                }
112
113                return new CodeValidationResult()
114                                .setCode(theCode)
115                                .setSeverity(IssueSeverity.INFORMATION)
116                                .setMessage("Code " + theCodeSystem + "#" + theCode
117                                                + " was not checked because the CodeSystem is not available");
118        }
119
120        /**
121         * Returns true if non existent code systems will still validate.
122         * False if they will throw errors.
123         * @return
124         */
125        private boolean allowNonExistentCodeSystems() {
126                switch (myNonExistentCodeSystemSeverity) {
127                        case ERROR:
128                        case FATAL:
129                                return false;
130                        case WARNING:
131                        case INFORMATION:
132                                return true;
133                        default:
134                                ourLog.info("Unknown issue severity " + myNonExistentCodeSystemSeverity.name()
135                                                + ". Treating as INFO/WARNING");
136                                return true;
137                }
138        }
139
140        /**
141         * Determines if the code system can (and should) be validated.
142         * @param theValidationSupportContext
143         * @param theCodeSystem
144         * @return
145         */
146        private boolean canValidateCodeSystem(ValidationSupportContext theValidationSupportContext, String theCodeSystem) {
147                if (!allowNonExistentCodeSystems()) {
148                        return false;
149                }
150                if (theCodeSystem == null) {
151                        return false;
152                }
153                IBaseResource codeSystem =
154                                theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theCodeSystem);
155                if (codeSystem != null) {
156                        return false;
157                }
158                return true;
159        }
160
161        /**
162         * If set to allow, code system violations will be flagged with Warning by default.
163         * Use setNonExistentCodeSystemSeverity instead.
164         */
165        @Deprecated
166        public void setAllowNonExistentCodeSystem(boolean theAllowNonExistentCodeSystem) {
167                if (theAllowNonExistentCodeSystem) {
168                        setNonExistentCodeSystemSeverity(IssueSeverity.WARNING);
169                } else {
170                        setNonExistentCodeSystemSeverity(IssueSeverity.ERROR);
171                }
172        }
173
174        /**
175         * Sets the non-existent code system severity.
176         */
177        public void setNonExistentCodeSystemSeverity(@Nonnull IssueSeverity theSeverity) {
178                Validate.notNull(theSeverity, "theSeverity must not be null");
179                myNonExistentCodeSystemSeverity = theSeverity;
180        }
181}