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