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}