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 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 public List<String> getExtensionDomains() { 192 return myExtensionDomains; 193 } 194 195 @Override 196 protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) { 197 VersionSpecificWorkerContextWrapper wrappedWorkerContext = provideWorkerContext(); 198 199 return new ValidatorWrapper() 200 .setAnyExtensionsAllowed(isAnyExtensionsAllowed()) 201 .setBestPracticeWarningLevel(getBestPracticeWarningLevel()) 202 .setErrorForUnknownProfiles(isErrorForUnknownProfiles()) 203 .setExtensionDomains(getExtensionDomains()) 204 .setNoTerminologyChecks(isNoTerminologyChecks()) 205 .setValidatorResourceFetcher(getValidatorResourceFetcher()) 206 .setAssumeValidRestReferences(isAssumeValidRestReferences()) 207 .validate(wrappedWorkerContext, theValidationCtx); 208 } 209 210 @Nonnull 211 protected VersionSpecificWorkerContextWrapper provideWorkerContext() { 212 VersionSpecificWorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext; 213 if (wrappedWorkerContext == null) { 214 VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter; 215 216 switch (myValidationSupport.getFhirContext().getVersion().getVersion()) { 217 case DSTU2: 218 case DSTU2_HL7ORG: { 219 converter = new VersionSpecificWorkerContextWrapper.IVersionTypeConverter() { 220 @Override 221 public Resource toCanonical(IBaseResource theNonCanonical) { 222 IBaseResource nonCanonical = theNonCanonical; 223 Resource retVal = VersionConvertor_10_50.convertResource((org.hl7.fhir.dstu2.model.Resource) nonCanonical); 224 if (nonCanonical instanceof org.hl7.fhir.dstu2.model.ValueSet) { 225 org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) nonCanonical; 226 if (valueSet.hasCodeSystem() && valueSet.getCodeSystem().hasSystem()) { 227 if (!valueSet.hasCompose()) { 228 ValueSet valueSetR5 = (ValueSet) retVal; 229 valueSetR5.getCompose().addInclude().setSystem(valueSet.getCodeSystem().getSystem()); 230 } 231 } 232 } 233 return retVal; 234 } 235 236 @Override 237 public IBaseResource fromCanonical(Resource theCanonical) { 238 IBaseResource canonical = VersionConvertor_10_50.convertResource(theCanonical); 239 return canonical; 240 } 241 }; 242 break; 243 } 244 245 case DSTU2_1: { 246 converter = new VersionTypeConverterDstu21(); 247 break; 248 } 249 250 case DSTU3: { 251 converter = new VersionTypeConverterDstu3(); 252 break; 253 } 254 255 case R4: { 256 converter = new VersionTypeConverterR4(); 257 break; 258 } 259 260 case R5: { 261 converter = VersionSpecificWorkerContextWrapper.IDENTITY_VERSION_TYPE_CONVERTER; 262 break; 263 } 264 265 default: 266 throw new IllegalStateException(); 267 } 268 269 wrappedWorkerContext = new VersionSpecificWorkerContextWrapper(new ValidationSupportContext(myValidationSupport), converter); 270 } 271 myWrappedWorkerContext = wrappedWorkerContext; 272 return wrappedWorkerContext; 273 } 274 275 public IResourceValidator.IValidatorResourceFetcher getValidatorResourceFetcher() { 276 return validatorResourceFetcher; 277 } 278 279 public void setValidatorResourceFetcher(IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher) { 280 this.validatorResourceFetcher = validatorResourceFetcher; 281 } 282 283 public boolean isAssumeValidRestReferences() { 284 return assumeValidRestReferences; 285 } 286 287 public void setAssumeValidRestReferences(boolean assumeValidRestReferences) { 288 this.assumeValidRestReferences = assumeValidRestReferences; 289 } 290 291 /** 292 * Clear any cached data held by the validator or any of its internal stores. This is mostly intended 293 * for unit tests, but could be used for production uses too. 294 */ 295 public void invalidateCaches() { 296 myValidationSupport.invalidateCaches(); 297 if (myWrappedWorkerContext != null) { 298 myWrappedWorkerContext.invalidateCaches(); 299 } 300 } 301 302 303 public static class NullEvaluationContext implements FHIRPathEngine.IEvaluationContext { 304 @Override 305 public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException { 306 return null; 307 } 308 309 @Override 310 public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { 311 return null; 312 } 313 314 @Override 315 public boolean log(String argument, List<Base> focus) { 316 return false; 317 } 318 319 @Override 320 public FunctionDetails resolveFunction(String functionName) { 321 return null; 322 } 323 324 @Override 325 public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException { 326 return null; 327 } 328 329 @Override 330 public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) { 331 return null; 332 } 333 334 @Override 335 public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException { 336 return null; 337 } 338 339 @Override 340 public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException { 341 return false; 342 } 343 344 @Override 345 public ValueSet resolveValueSet(Object appContext, String url) { 346 return null; 347 } 348 } 349 350 351}