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