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                result.setMessage("CodeSystem is unknown and can't be validated: " + theCodeSystem);
080
081                // For information level, we just strip out the severity+message entirely
082                // so that nothing appears in the validation result
083                if (myNonExistentCodeSystemSeverity == IssueSeverity.INFORMATION) {
084                        result.setCode(theCode);
085                        result.setSeverity(null);
086                        result.setMessage(null);
087                }
088
089                return result;
090        }
091
092        @Nullable
093        @Override
094        public CodeValidationResult validateCodeInValueSet(
095                        ValidationSupportContext theValidationSupportContext,
096                        ConceptValidationOptions theOptions,
097                        String theCodeSystem,
098                        String theCode,
099                        String theDisplay,
100                        @Nonnull IBaseResource theValueSet) {
101                if (!canValidateCodeSystem(theValidationSupportContext, theCodeSystem)) {
102                        return null;
103                }
104
105                return new CodeValidationResult()
106                                .setCode(theCode)
107                                .setSeverity(IssueSeverity.INFORMATION)
108                                .setMessage("Code " + theCodeSystem + "#" + theCode
109                                                + " was not checked because the CodeSystem is not available");
110        }
111
112        /**
113         * Returns true if non existent code systems will still validate.
114         * False if they will throw errors.
115         * @return
116         */
117        private boolean allowNonExistentCodeSystems() {
118                switch (myNonExistentCodeSystemSeverity) {
119                        case ERROR:
120                        case FATAL:
121                                return false;
122                        case WARNING:
123                        case INFORMATION:
124                                return true;
125                        default:
126                                ourLog.info("Unknown issue severity " + myNonExistentCodeSystemSeverity.name()
127                                                + ". Treating as INFO/WARNING");
128                                return true;
129                }
130        }
131
132        /**
133         * Determines if the code system can (and should) be validated.
134         * @param theValidationSupportContext
135         * @param theCodeSystem
136         * @return
137         */
138        private boolean canValidateCodeSystem(ValidationSupportContext theValidationSupportContext, String theCodeSystem) {
139                if (!allowNonExistentCodeSystems()) {
140                        return false;
141                }
142                if (theCodeSystem == null) {
143                        return false;
144                }
145                IBaseResource codeSystem =
146                                theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theCodeSystem);
147                if (codeSystem != null) {
148                        return false;
149                }
150                return true;
151        }
152
153        /**
154         * If set to allow, code system violations will be flagged with Warning by default.
155         * Use setNonExistentCodeSystemSeverity instead.
156         */
157        @Deprecated
158        public void setAllowNonExistentCodeSystem(boolean theAllowNonExistentCodeSystem) {
159                if (theAllowNonExistentCodeSystem) {
160                        setNonExistentCodeSystemSeverity(IssueSeverity.WARNING);
161                } else {
162                        setNonExistentCodeSystemSeverity(IssueSeverity.ERROR);
163                }
164        }
165
166        /**
167         * Sets the non-existent code system severity.
168         */
169        public void setNonExistentCodeSystemSeverity(@Nonnull IssueSeverity theSeverity) {
170                Validate.notNull(theSeverity, "theSeverity must not be null");
171                myNonExistentCodeSystemSeverity = theSeverity;
172        }
173}