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