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}