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.context.support.ValidationSupportContext; 008import ca.uhn.fhir.validation.IInstanceValidatorModule; 009import ca.uhn.fhir.validation.IValidationContext; 010import org.apache.commons.lang3.Validate; 011import org.hl7.fhir.convertors.VersionConvertor_10_50; 012import org.hl7.fhir.exceptions.FHIRException; 013import org.hl7.fhir.exceptions.PathEngineException; 014import org.hl7.fhir.instance.model.api.IBaseResource; 015import org.hl7.fhir.r5.model.Base; 016import org.hl7.fhir.r5.model.Resource; 017import org.hl7.fhir.r5.model.TypeDetails; 018import org.hl7.fhir.r5.model.ValueSet; 019import org.hl7.fhir.r5.utils.FHIRPathEngine; 020import org.hl7.fhir.r5.utils.IResourceValidator; 021import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel; 022import org.hl7.fhir.utilities.validation.ValidationMessage; 023 024import javax.annotation.Nonnull; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.List; 028 029@SuppressWarnings({"PackageAccessibility", "Duplicates"}) 030public class FhirInstanceValidator extends BaseValidatorBridge implements IInstanceValidatorModule { 031 032 private boolean myAnyExtensionsAllowed = true; 033 private BestPracticeWarningLevel myBestPracticeWarningLevel; 034 private IValidationSupport myValidationSupport; 035 private boolean noTerminologyChecks = false; 036 private volatile VersionSpecificWorkerContextWrapper myWrappedWorkerContext; 037 private boolean errorForUnknownProfiles; 038 private boolean assumeValidRestReferences; 039 private List<String> myExtensionDomains = Collections.emptyList(); 040 private IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher; 041 private volatile FhirContext myDstu2Context; 042 private volatile FhirContext myHl7OrgDstu2Context; 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 * @return 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 * If set to {@literal true} (default is true) extensions which are not known to the 157 * validator (e.g. because they have not been explicitly declared in a profile) will 158 * be validated but will not cause an error. 159 */ 160 public boolean isAnyExtensionsAllowed() { 161 return myAnyExtensionsAllowed; 162 } 163 164 /** 165 * If set to {@literal true} (default is true) extensions which are not known to the 166 * validator (e.g. because they have not been explicitly declared in a profile) will 167 * be validated but will not cause an error. 168 */ 169 public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) { 170 myAnyExtensionsAllowed = theAnyExtensionsAllowed; 171 } 172 173 public boolean isErrorForUnknownProfiles() { 174 return errorForUnknownProfiles; 175 } 176 177 public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) { 178 this.errorForUnknownProfiles = errorForUnknownProfiles; 179 } 180 181 /** 182 * If set to {@literal true} (default is false) the valueSet will not be validate 183 */ 184 public boolean isNoTerminologyChecks() { 185 return noTerminologyChecks; 186 } 187 188 /** 189 * If set to {@literal true} (default is false) the valueSet will not be validate 190 */ 191 public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) { 192 noTerminologyChecks = theNoTerminologyChecks; 193 } 194 195 public List<String> getExtensionDomains() { 196 return myExtensionDomains; 197 } 198 199 @Override 200 protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) { 201 VersionSpecificWorkerContextWrapper wrappedWorkerContext = provideWorkerContext(); 202 203 return new ValidatorWrapper() 204 .setAnyExtensionsAllowed(isAnyExtensionsAllowed()) 205 .setBestPracticeWarningLevel(getBestPracticeWarningLevel()) 206 .setErrorForUnknownProfiles(isErrorForUnknownProfiles()) 207 .setExtensionDomains(getExtensionDomains()) 208 .setNoTerminologyChecks(isNoTerminologyChecks()) 209 .setValidatorResourceFetcher(getValidatorResourceFetcher()) 210 .setAssumeValidRestReferences(isAssumeValidRestReferences()) 211 .validate(wrappedWorkerContext, theValidationCtx); 212 } 213 214 @Nonnull 215 protected VersionSpecificWorkerContextWrapper provideWorkerContext() { 216 VersionSpecificWorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext; 217 if (wrappedWorkerContext == null) { 218 VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter; 219 220 switch (myValidationSupport.getFhirContext().getVersion().getVersion()) { 221 case DSTU2: 222 case DSTU2_HL7ORG: { 223 converter = new VersionSpecificWorkerContextWrapper.IVersionTypeConverter() { 224 @Override 225 public Resource toCanonical(IBaseResource theNonCanonical) { 226 IBaseResource nonCanonical = theNonCanonical; 227 Resource retVal = VersionConvertor_10_50.convertResource((org.hl7.fhir.dstu2.model.Resource) nonCanonical); 228 if (nonCanonical instanceof org.hl7.fhir.dstu2.model.ValueSet) { 229 org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) nonCanonical; 230 if (valueSet.hasCodeSystem() && valueSet.getCodeSystem().hasSystem()) { 231 if (!valueSet.hasCompose()) { 232 ValueSet valueSetR5 = (ValueSet) retVal; 233 valueSetR5.getCompose().addInclude().setSystem(valueSet.getCodeSystem().getSystem()); 234 } 235 } 236 } 237 return retVal; 238 } 239 240 @Override 241 public IBaseResource fromCanonical(Resource theCanonical) { 242 IBaseResource canonical = VersionConvertor_10_50.convertResource(theCanonical); 243 return canonical; 244 } 245 }; 246 break; 247 } 248 249 case DSTU2_1: { 250 converter = new VersionTypeConverterDstu21(); 251 break; 252 } 253 254 case DSTU3: { 255 converter = new VersionTypeConverterDstu3(); 256 break; 257 } 258 259 case R4: { 260 converter = new VersionTypeConverterR4(); 261 break; 262 } 263 264 case R5: { 265 converter = VersionSpecificWorkerContextWrapper.IDENTITY_VERSION_TYPE_CONVERTER; 266 break; 267 } 268 269 default: 270 throw new IllegalStateException(); 271 } 272 273 wrappedWorkerContext = new VersionSpecificWorkerContextWrapper(new ValidationSupportContext(myValidationSupport), converter); 274 } 275 myWrappedWorkerContext = wrappedWorkerContext; 276 return wrappedWorkerContext; 277 } 278 279 private FhirContext getDstu2Context() { 280 FhirContext dstu2Context = myDstu2Context; 281 if (dstu2Context == null) { 282 dstu2Context = FhirContext.forDstu2(); 283 myDstu2Context = dstu2Context; 284 } 285 return dstu2Context; 286 } 287 288 private FhirContext getHl7OrgDstu2Context() { 289 FhirContext hl7OrgDstu2Context = myHl7OrgDstu2Context; 290 if (hl7OrgDstu2Context == null) { 291 hl7OrgDstu2Context = FhirContext.forDstu2Hl7Org(); 292 myHl7OrgDstu2Context = hl7OrgDstu2Context; 293 } 294 return hl7OrgDstu2Context; 295 } 296 297 public IResourceValidator.IValidatorResourceFetcher getValidatorResourceFetcher() { 298 return validatorResourceFetcher; 299 } 300 301 public void setValidatorResourceFetcher(IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher) { 302 this.validatorResourceFetcher = validatorResourceFetcher; 303 } 304 305 public boolean isAssumeValidRestReferences() { 306 return assumeValidRestReferences; 307 } 308 309 public void setAssumeValidRestReferences(boolean assumeValidRestReferences) { 310 this.assumeValidRestReferences = assumeValidRestReferences; 311 } 312 313 /** 314 * Clear any cached data held by the validator or any of its internal stores. This is mostly intended 315 * for unit tests, but could be used for production uses too. 316 */ 317 public void invalidateCaches() { 318 myValidationSupport.invalidateCaches(); 319 if (myWrappedWorkerContext != null) { 320 myWrappedWorkerContext.invalidateCaches(); 321 } 322 } 323 324 325 public static class NullEvaluationContext implements FHIRPathEngine.IEvaluationContext { 326 @Override 327 public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException { 328 return null; 329 } 330 331 @Override 332 public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { 333 return null; 334 } 335 336 @Override 337 public boolean log(String argument, List<Base> focus) { 338 return false; 339 } 340 341 @Override 342 public FunctionDetails resolveFunction(String functionName) { 343 return null; 344 } 345 346 @Override 347 public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException { 348 return null; 349 } 350 351 @Override 352 public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) { 353 return null; 354 } 355 356 @Override 357 public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException { 358 return null; 359 } 360 361 @Override 362 public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException { 363 return false; 364 } 365 366 @Override 367 public ValueSet resolveValueSet(Object appContext, String url) { 368 return null; 369 } 370 } 371 372 373}