001package ca.uhn.fhir.validation; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2021 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.interceptor.api.HookParams; 025import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; 026import ca.uhn.fhir.interceptor.api.Pointcut; 027import ca.uhn.fhir.validation.schematron.SchematronProvider; 028import org.apache.commons.lang3.Validate; 029import org.hl7.fhir.instance.model.api.IBaseResource; 030 031import java.util.ArrayList; 032import java.util.Iterator; 033import java.util.List; 034 035 036/** 037 * Resource validator, which checks resources for compliance against various validation schemes (schemas, schematrons, profiles, etc.) 038 * 039 * <p> 040 * To obtain a resource validator, call {@link FhirContext#newValidator()} 041 * </p> 042 * 043 * <p> 044 * <b>Thread safety note:</b> This class is thread safe, so you may register or unregister validator modules at any time. Individual modules are not guaranteed to be thread safe however. Reconfigure 045 * them with caution. 046 * </p> 047 */ 048public class FhirValidator { 049 050 private static final String I18N_KEY_NO_PH_ERROR = FhirValidator.class.getName() + ".noPhError"; 051 052 private static volatile Boolean ourPhPresentOnClasspath; 053 private final FhirContext myContext; 054 private List<IValidatorModule> myValidators = new ArrayList<>(); 055 private IInterceptorBroadcaster myInterceptorBraodcaster; 056 057 /** 058 * Constructor (this should not be called directly, but rather {@link FhirContext#newValidator()} should be called to obtain an instance of {@link FhirValidator}) 059 */ 060 public FhirValidator(FhirContext theFhirContext) { 061 myContext = theFhirContext; 062 063 if (ourPhPresentOnClasspath == null) { 064 ourPhPresentOnClasspath = SchematronProvider.isSchematronAvailable(theFhirContext); 065 } 066 } 067 068 private void addOrRemoveValidator(boolean theValidateAgainstStandardSchema, Class<? extends IValidatorModule> type, IValidatorModule theInstance) { 069 if (theValidateAgainstStandardSchema) { 070 boolean found = haveValidatorOfType(type); 071 if (!found) { 072 registerValidatorModule(theInstance); 073 } 074 } else { 075 for (Iterator<IValidatorModule> iter = myValidators.iterator(); iter.hasNext(); ) { 076 IValidatorModule next = iter.next(); 077 if (next.getClass().equals(type)) { 078 unregisterValidatorModule(next); 079 } 080 } 081 } 082 } 083 084 private boolean haveValidatorOfType(Class<? extends IValidatorModule> type) { 085 boolean found = false; 086 for (IValidatorModule next : myValidators) { 087 if (next.getClass().equals(type)) { 088 found = true; 089 } 090 } 091 return found; 092 } 093 094 /** 095 * Should the validator validate the resource against the base schema (the schema provided with the FHIR distribution itself) 096 */ 097 public synchronized boolean isValidateAgainstStandardSchema() { 098 return haveValidatorOfType(SchemaBaseValidator.class); 099 } 100 101 /** 102 * Should the validator validate the resource against the base schema (the schema provided with the FHIR distribution itself) 103 * 104 * @return Returns a referens to <code>this<code> for method chaining 105 */ 106 public synchronized FhirValidator setValidateAgainstStandardSchema(boolean theValidateAgainstStandardSchema) { 107 addOrRemoveValidator(theValidateAgainstStandardSchema, SchemaBaseValidator.class, new SchemaBaseValidator(myContext)); 108 return this; 109 } 110 111 /** 112 * Should the validator validate the resource against the base schema (the schema provided with the FHIR distribution itself) 113 */ 114 public synchronized boolean isValidateAgainstStandardSchematron() { 115 if (!ourPhPresentOnClasspath) { 116 // No need to ask since we dont have Ph-Schematron. Also Class.forname will complain 117 // about missing ph-schematron import. 118 return false; 119 } 120 Class<? extends IValidatorModule> cls = SchematronProvider.getSchematronValidatorClass(); 121 return haveValidatorOfType(cls); 122 } 123 124 /** 125 * Should the validator validate the resource against the base schematron (the schematron provided with the FHIR distribution itself) 126 * 127 * @return Returns a referens to <code>this<code> for method chaining 128 */ 129 public synchronized FhirValidator setValidateAgainstStandardSchematron(boolean theValidateAgainstStandardSchematron) { 130 if (theValidateAgainstStandardSchematron && !ourPhPresentOnClasspath) { 131 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_KEY_NO_PH_ERROR)); 132 } 133 if (!theValidateAgainstStandardSchematron && !ourPhPresentOnClasspath) { 134 return this; 135 } 136 Class<? extends IValidatorModule> cls = SchematronProvider.getSchematronValidatorClass(); 137 IValidatorModule instance = SchematronProvider.getSchematronValidatorInstance(myContext); 138 addOrRemoveValidator(theValidateAgainstStandardSchematron, cls, instance); 139 return this; 140 } 141 142 /** 143 * Add a new validator module to this validator. You may register as many modules as you like at any time. 144 * 145 * @param theValidator The validator module. Must not be null. 146 * @return Returns a reference to <code>this</code> for easy method chaining. 147 */ 148 public synchronized FhirValidator registerValidatorModule(IValidatorModule theValidator) { 149 Validate.notNull(theValidator, "theValidator must not be null"); 150 ArrayList<IValidatorModule> newValidators = new ArrayList<IValidatorModule>(myValidators.size() + 1); 151 newValidators.addAll(myValidators); 152 newValidators.add(theValidator); 153 154 myValidators = newValidators; 155 return this; 156 } 157 158 /** 159 * Removes a validator module from this validator. You may register as many modules as you like, and remove them at any time. 160 * 161 * @param theValidator The validator module. Must not be null. 162 */ 163 public synchronized void unregisterValidatorModule(IValidatorModule theValidator) { 164 Validate.notNull(theValidator, "theValidator must not be null"); 165 ArrayList<IValidatorModule> newValidators = new ArrayList<IValidatorModule>(myValidators.size() + 1); 166 newValidators.addAll(myValidators); 167 newValidators.remove(theValidator); 168 169 myValidators = newValidators; 170 } 171 172 173 private void applyDefaultValidators() { 174 if (myValidators.isEmpty()) { 175 setValidateAgainstStandardSchema(true); 176 if (ourPhPresentOnClasspath) { 177 setValidateAgainstStandardSchematron(true); 178 } 179 } 180 } 181 182 183 /** 184 * Validates a resource instance returning a {@link ValidationResult} which contains the results. 185 * 186 * @param theResource the resource to validate 187 * @return the results of validation 188 * @since 0.7 189 */ 190 public ValidationResult validateWithResult(IBaseResource theResource) { 191 return validateWithResult(theResource, null); 192 } 193 194 /** 195 * Validates a resource instance returning a {@link ValidationResult} which contains the results. 196 * 197 * @param theResource the resource to validate 198 * @return the results of validation 199 * @since 1.1 200 */ 201 public ValidationResult validateWithResult(String theResource) { 202 return validateWithResult(theResource, null); 203 } 204 205 /** 206 * Validates a resource instance returning a {@link ValidationResult} which contains the results. 207 * 208 * @param theResource the resource to validate 209 * @param theOptions Optionally provides options to the validator 210 * @return the results of validation 211 * @since 4.0.0 212 */ 213 public ValidationResult validateWithResult(IBaseResource theResource, ValidationOptions theOptions) { 214 Validate.notNull(theResource, "theResource must not be null"); 215 216 applyDefaultValidators(); 217 218 IValidationContext<IBaseResource> ctx = ValidationContext.forResource(myContext, theResource, theOptions); 219 220 for (IValidatorModule next : myValidators) { 221 next.validateResource(ctx); 222 } 223 224 ValidationResult result = ctx.toResult(); 225 result = invokeValidationCompletedHooks(theResource, null, result); 226 return result; 227 } 228 229 private ValidationResult invokeValidationCompletedHooks(IBaseResource theResourceParsed, String theResourceRaw, ValidationResult theValidationResult) { 230 if (myInterceptorBraodcaster != null) { 231 if (myInterceptorBraodcaster.hasHooks(Pointcut.VALIDATION_COMPLETED)) { 232 HookParams params = new HookParams() 233 .add(IBaseResource.class, theResourceParsed) 234 .add(String.class, theResourceRaw) 235 .add(ValidationResult.class, theValidationResult); 236 Object newResult = myInterceptorBraodcaster.callHooksAndReturnObject(Pointcut.VALIDATION_COMPLETED, params); 237 if (newResult != null) { 238 theValidationResult = (ValidationResult) newResult; 239 } 240 } 241 } 242 return theValidationResult; 243 } 244 245 /** 246 * Validates a resource instance returning a {@link ValidationResult} which contains the results. 247 * 248 * @param theResource the resource to validate 249 * @param theOptions Optionally provides options to the validator 250 * @return the results of validation 251 * @since 4.0.0 252 */ 253 public ValidationResult validateWithResult(String theResource, ValidationOptions theOptions) { 254 Validate.notNull(theResource, "theResource must not be null"); 255 256 applyDefaultValidators(); 257 258 IValidationContext<IBaseResource> ctx = ValidationContext.forText(myContext, theResource, theOptions); 259 260 for (IValidatorModule next : myValidators) { 261 next.validateResource(ctx); 262 } 263 264 ValidationResult result = ctx.toResult(); 265 result = invokeValidationCompletedHooks(null, theResource, result); 266 return result; 267 } 268 269 /** 270 * Optionally supplies an interceptor broadcaster that will be used to invoke validation related Pointcut events 271 * 272 * @since 5.5.0 273 */ 274 public void setInterceptorBroadcaster(IInterceptorBroadcaster theInterceptorBraodcaster) { 275 myInterceptorBraodcaster = theInterceptorBraodcaster; 276 } 277}