001package org.hl7.fhir.common.hapi.validation.validator; 002 003import ca.uhn.fhir.context.FhirContext; 004import ca.uhn.fhir.context.FhirVersionEnum; 005import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; 006import ca.uhn.fhir.context.support.IValidationSupport; 007import ca.uhn.fhir.validation.IInstanceValidatorModule; 008import ca.uhn.fhir.validation.IValidationContext; 009import jakarta.annotation.Nonnull; 010import org.apache.commons.lang3.Validate; 011import org.hl7.fhir.exceptions.FHIRException; 012import org.hl7.fhir.exceptions.PathEngineException; 013import org.hl7.fhir.r5.model.Base; 014import org.hl7.fhir.r5.model.TypeDetails; 015import org.hl7.fhir.r5.model.ValueSet; 016import org.hl7.fhir.r5.utils.FHIRPathEngine; 017import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FunctionDetails; 018import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; 019import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; 020import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; 021import org.hl7.fhir.utilities.validation.ValidationMessage; 022 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.List; 026 027@SuppressWarnings({"PackageAccessibility", "Duplicates"}) 028public class FhirInstanceValidator extends BaseValidatorBridge implements IInstanceValidatorModule { 029 030 private boolean myAnyExtensionsAllowed = true; 031 private BestPracticeWarningLevel myBestPracticeWarningLevel; 032 private IValidationSupport myValidationSupport; 033 private boolean noTerminologyChecks = false; 034 private boolean noExtensibleWarnings = false; 035 private boolean noBindingMsgSuppressed = false; 036 private volatile VersionSpecificWorkerContextWrapper myWrappedWorkerContext; 037 private boolean errorForUnknownProfiles = true; 038 private boolean assumeValidRestReferences; 039 private List<String> myExtensionDomains = Collections.emptyList(); 040 private IValidatorResourceFetcher validatorResourceFetcher; 041 private IValidationPolicyAdvisor validatorPolicyAdvisor; 042 043 /** 044 * Constructor 045 * <p> 046 * Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support} 047 */ 048 public FhirInstanceValidator(FhirContext theContext) { 049 this(theContext.getValidationSupport()); 050 } 051 052 /** 053 * Constructor which uses the given validation support 054 * 055 * @param theValidationSupport The validation support 056 */ 057 public FhirInstanceValidator(IValidationSupport theValidationSupport) { 058 if (theValidationSupport.getFhirContext().getVersion().getVersion() == FhirVersionEnum.DSTU2) { 059 myValidationSupport = new HapiToHl7OrgDstu2ValidatingSupportWrapper(theValidationSupport); 060 } else { 061 myValidationSupport = theValidationSupport; 062 } 063 } 064 065 /** 066 * Every element in a resource or data type includes an optional <it>extension</it> child element 067 * which is identified by it's {@code url attribute}. There exists a number of predefined 068 * extension urls or extension domains:<ul> 069 * <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li> 070 * <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li> 071 * </ul> 072 * It is possible to extend this list of known extension by defining custom extensions: 073 * Any url which starts which one of the elements in the list of custom extension domains is 074 * considered as known. 075 * <p> 076 * Any unknown extension domain will result in an information message when validating a resource. 077 * </p> 078 */ 079 public FhirInstanceValidator setCustomExtensionDomains(List<String> extensionDomains) { 080 this.myExtensionDomains = extensionDomains; 081 return this; 082 } 083 084 /** 085 * Every element in a resource or data type includes an optional <it>extension</it> child element 086 * which is identified by it's {@code url attribute}. There exists a number of predefined 087 * extension urls or extension domains:<ul> 088 * <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li> 089 * <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li> 090 * </ul> 091 * It is possible to extend this list of known extension by defining custom extensions: 092 * Any url which starts which one of the elements in the list of custom extension domains is 093 * considered as known. 094 * <p> 095 * Any unknown extension domain will result in an information message when validating a resource. 096 * </p> 097 */ 098 public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) { 099 this.myExtensionDomains = Arrays.asList(extensionDomains); 100 return this; 101 } 102 103 /** 104 * Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}). 105 * <p> 106 * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is 107 * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be 108 * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice 109 * guielines will be ignored. 110 * </p> 111 * 112 * @see #setBestPracticeWarningLevel(BestPracticeWarningLevel) 113 */ 114 public BestPracticeWarningLevel getBestPracticeWarningLevel() { 115 return myBestPracticeWarningLevel; 116 } 117 118 /** 119 * Sets the "best practice warning level". When validating, any deviations from best practices will be reported at 120 * this level. 121 * <p> 122 * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is 123 * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be 124 * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice 125 * guielines will be ignored. 126 * </p> 127 * 128 * @param theBestPracticeWarningLevel The level, must not be <code>null</code> 129 */ 130 public void setBestPracticeWarningLevel(BestPracticeWarningLevel theBestPracticeWarningLevel) { 131 Validate.notNull(theBestPracticeWarningLevel); 132 myBestPracticeWarningLevel = theBestPracticeWarningLevel; 133 } 134 135 /** 136 * Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of 137 * DefaultProfileValidationSupport if the no-arguments constructor for this object was used. 138 */ 139 public IValidationSupport getValidationSupport() { 140 return myValidationSupport; 141 } 142 143 /** 144 * Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of 145 * DefaultProfileValidationSupport if the no-arguments constructor for this object was used. 146 */ 147 public void setValidationSupport(IValidationSupport theValidationSupport) { 148 myValidationSupport = theValidationSupport; 149 myWrappedWorkerContext = null; 150 } 151 152 /** 153 * If set to {@literal true} (default is true) extensions which are not known to the 154 * validator (e.g. because they have not been explicitly declared in a profile) will 155 * be validated but will not cause an error. 156 */ 157 public boolean isAnyExtensionsAllowed() { 158 return myAnyExtensionsAllowed; 159 } 160 161 /** 162 * If set to {@literal true} (default is true) extensions which are not known to the 163 * validator (e.g. because they have not been explicitly declared in a profile) will 164 * be validated but will not cause an error. 165 */ 166 public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) { 167 myAnyExtensionsAllowed = theAnyExtensionsAllowed; 168 } 169 170 public boolean isErrorForUnknownProfiles() { 171 return errorForUnknownProfiles; 172 } 173 174 public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) { 175 this.errorForUnknownProfiles = errorForUnknownProfiles; 176 } 177 178 /** 179 * If set to {@literal true} (default is false) the valueSet will not be validate 180 */ 181 public boolean isNoTerminologyChecks() { 182 return noTerminologyChecks; 183 } 184 185 /** 186 * If set to {@literal true} (default is false) the valueSet will not be validate 187 */ 188 public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) { 189 noTerminologyChecks = theNoTerminologyChecks; 190 } 191 192 /** 193 * If set to {@literal true} (default is false) no extensible warnings suppressed 194 */ 195 public boolean isNoExtensibleWarnings() { 196 return noExtensibleWarnings; 197 } 198 199 /** 200 * If set to {@literal true} (default is false) no extensible warnings is suppressed 201 */ 202 public void setNoExtensibleWarnings(final boolean theNoExtensibleWarnings) { 203 noExtensibleWarnings = theNoExtensibleWarnings; 204 } 205 206 /** 207 * If set to {@literal true} (default is false) no binding message is suppressed 208 */ 209 public boolean isNoBindingMsgSuppressed() { 210 return noBindingMsgSuppressed; 211 } 212 213 /** 214 * If set to {@literal true} (default is false) no binding message is suppressed 215 */ 216 public void setNoBindingMsgSuppressed(final boolean theNoBindingMsgSuppressed) { 217 noBindingMsgSuppressed = theNoBindingMsgSuppressed; 218 } 219 220 public List<String> getExtensionDomains() { 221 return myExtensionDomains; 222 } 223 224 @Override 225 protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) { 226 VersionSpecificWorkerContextWrapper wrappedWorkerContext = provideWorkerContext(); 227 228 return new ValidatorWrapper() 229 .setAnyExtensionsAllowed(isAnyExtensionsAllowed()) 230 .setBestPracticeWarningLevel(getBestPracticeWarningLevel()) 231 .setErrorForUnknownProfiles(isErrorForUnknownProfiles()) 232 .setExtensionDomains(getExtensionDomains()) 233 .setValidationPolicyAdvisor(validatorPolicyAdvisor) 234 .setNoTerminologyChecks(isNoTerminologyChecks()) 235 .setNoExtensibleWarnings(isNoExtensibleWarnings()) 236 .setNoBindingMsgSuppressed(isNoBindingMsgSuppressed()) 237 .setValidatorResourceFetcher(getValidatorResourceFetcher()) 238 .setAssumeValidRestReferences(isAssumeValidRestReferences()) 239 .validate(wrappedWorkerContext, theValidationCtx); 240 } 241 242 @Nonnull 243 protected VersionSpecificWorkerContextWrapper provideWorkerContext() { 244 VersionSpecificWorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext; 245 if (wrappedWorkerContext == null) { 246 wrappedWorkerContext = 247 VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(myValidationSupport); 248 } 249 myWrappedWorkerContext = wrappedWorkerContext; 250 return wrappedWorkerContext; 251 } 252 253 public IValidationPolicyAdvisor getValidatorPolicyAdvisor() { 254 return validatorPolicyAdvisor; 255 } 256 257 public void setValidatorPolicyAdvisor(IValidationPolicyAdvisor validatorPolicyAdvisor) { 258 this.validatorPolicyAdvisor = validatorPolicyAdvisor; 259 } 260 261 public IValidatorResourceFetcher getValidatorResourceFetcher() { 262 return validatorResourceFetcher; 263 } 264 265 public void setValidatorResourceFetcher(IValidatorResourceFetcher validatorResourceFetcher) { 266 this.validatorResourceFetcher = validatorResourceFetcher; 267 } 268 269 public boolean isAssumeValidRestReferences() { 270 return assumeValidRestReferences; 271 } 272 273 public void setAssumeValidRestReferences(boolean assumeValidRestReferences) { 274 this.assumeValidRestReferences = assumeValidRestReferences; 275 } 276 277 /** 278 * Clear any cached data held by the validator or any of its internal stores. This is mostly intended 279 * for unit tests, but could be used for production uses too. 280 */ 281 public void invalidateCaches() { 282 myValidationSupport.invalidateCaches(); 283 if (myWrappedWorkerContext != null) { 284 myWrappedWorkerContext.invalidateCaches(); 285 } 286 } 287 288 public static class NullEvaluationContext implements FHIRPathEngine.IEvaluationContext { 289 290 @Override 291 public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) 292 throws PathEngineException { 293 return Collections.emptyList(); 294 } 295 296 @Override 297 public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { 298 return null; 299 } 300 301 @Override 302 public boolean log(String argument, List<Base> focus) { 303 return false; 304 } 305 306 @Override 307 public FunctionDetails resolveFunction(String functionName) { 308 return null; 309 } 310 311 @Override 312 public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) 313 throws PathEngineException { 314 return null; 315 } 316 317 @Override 318 public List<Base> executeFunction( 319 Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) { 320 return null; 321 } 322 323 @Override 324 public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException { 325 return null; 326 } 327 328 @Override 329 public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException { 330 return false; 331 } 332 333 @Override 334 public ValueSet resolveValueSet(Object appContext, String url) { 335 return null; 336 } 337 } 338}