001package ca.uhn.fhir.validation; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2016 University Health Network 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 java.util.ArrayList; 024import java.util.Iterator; 025import java.util.List; 026 027import org.apache.commons.lang3.Validate; 028import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; 029import org.hl7.fhir.instance.model.api.IBaseResource; 030 031import ca.uhn.fhir.context.FhirContext; 032import ca.uhn.fhir.model.api.Bundle; 033import ca.uhn.fhir.model.api.IResource; 034import ca.uhn.fhir.util.OperationOutcomeUtil; 035import ca.uhn.fhir.validation.schematron.SchematronProvider; 036 037/** 038 * Resource validator, which checks resources for compliance against various validation schemes (schemas, schematrons, profiles, etc.) 039 * 040 * <p> 041 * To obtain a resource validator, call {@link FhirContext#newValidator()} 042 * </p> 043 * 044 * <p> 045 * <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 046 * them with caution. 047 * </p> 048 */ 049public class FhirValidator { 050 051 private static final String I18N_KEY_NO_PHLOC_ERROR = FhirValidator.class.getName() + ".noPhlocError"; 052 053 private static volatile Boolean ourPhlocPresentOnClasspath; 054 private final FhirContext myContext; 055 private List<IValidatorModule> myValidators = new ArrayList<IValidatorModule>(); 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 (ourPhlocPresentOnClasspath == null) { 064 ourPhlocPresentOnClasspath = SchematronProvider.isScematronAvailable(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 public synchronized boolean isValidateAgainstStandardSchematron() { 105 if (!ourPhlocPresentOnClasspath) { 106 return false; // No need to ask since we dont have Phloc. Also Class.forname will complain 107 // about missing phloc import. 108 } 109 Class<? extends IValidatorModule> cls = SchematronProvider.getSchematronValidatorClass(); 110 return haveValidatorOfType(cls); 111 } 112 113 /** 114 * Add a new validator module to this validator. You may register as many modules as you like at any time. 115 * 116 * @param theValidator 117 * The validator module. Must not be null. 118 */ 119 public synchronized void registerValidatorModule(IValidatorModule theValidator) { 120 Validate.notNull(theValidator, "theValidator must not be null"); 121 ArrayList<IValidatorModule> newValidators = new ArrayList<IValidatorModule>(myValidators.size() + 1); 122 newValidators.addAll(myValidators); 123 newValidators.add(theValidator); 124 125 myValidators = newValidators; 126 } 127 128 /** 129 * Should the validator validate the resource against the base schema (the schema provided with the FHIR distribution itself) 130 * 131 * @return Returns a referens to <code>this<code> for method chaining 132 */ 133 public synchronized FhirValidator setValidateAgainstStandardSchema(boolean theValidateAgainstStandardSchema) { 134 addOrRemoveValidator(theValidateAgainstStandardSchema, SchemaBaseValidator.class, new SchemaBaseValidator(myContext)); 135 return this; 136 } 137 138 /** 139 * Should the validator validate the resource against the base schematron (the schematron provided with the FHIR distribution itself) 140 * 141 * @return Returns a referens to <code>this<code> for method chaining 142 */ 143 public synchronized FhirValidator setValidateAgainstStandardSchematron(boolean theValidateAgainstStandardSchematron) { 144 if (theValidateAgainstStandardSchematron && !ourPhlocPresentOnClasspath) { 145 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_KEY_NO_PHLOC_ERROR)); 146 } 147 Class<? extends IValidatorModule> cls = SchematronProvider.getSchematronValidatorClass(); 148 IValidatorModule instance = SchematronProvider.getSchematronValidatorInstance(myContext); 149 addOrRemoveValidator(theValidateAgainstStandardSchematron, cls, instance); 150 return this; 151 } 152 153 /** 154 * Removes a validator module from this validator. You may register as many modules as you like, and remove them at any time. 155 * 156 * @param theValidator 157 * The validator module. Must not be null. 158 */ 159 public synchronized void unregisterValidatorModule(IValidatorModule theValidator) { 160 Validate.notNull(theValidator, "theValidator must not be null"); 161 ArrayList<IValidatorModule> newValidators = new ArrayList<IValidatorModule>(myValidators.size() + 1); 162 newValidators.addAll(myValidators); 163 newValidators.remove(theValidator); 164 165 myValidators = newValidators; 166 } 167 168 /** 169 * Validates a bundle instance, throwing a {@link ValidationFailureException} if the validation fails. This validation includes validation of all resources in the bundle. 170 * 171 * @param theBundle 172 * The resource to validate 173 * @throws ValidationFailureException 174 * If the validation fails 175 * @deprecated use {@link #validateWithResult(ca.uhn.fhir.model.api.Bundle)} instead 176 */ 177 @Deprecated 178 public void validate(Bundle theBundle) { 179 Validate.notNull(theBundle, "theBundle must not be null"); 180 181 applyDefaultValidators(); 182 183 IValidationContext<Bundle> ctx = ValidationContext.forBundle(myContext, theBundle); 184 185 for (IValidatorModule next : myValidators) { 186 next.validateBundle(ctx); 187 } 188 189 if (ctx.toResult().isSuccessful() == false ) { 190 IBaseOperationOutcome oo = ctx.toResult().toOperationOutcome(); 191 if (oo != null && OperationOutcomeUtil.hasIssues(myContext, oo)) { 192 throw new ValidationFailureException(myContext, oo); 193 } 194 } 195 196 } 197 198 private void applyDefaultValidators() { 199 if (myValidators.isEmpty()) { 200 setValidateAgainstStandardSchema(true); 201 if (ourPhlocPresentOnClasspath) { 202 setValidateAgainstStandardSchematron(true); 203 } 204 } 205 } 206 207 /** 208 * Validates a resource instance, throwing a {@link ValidationFailureException} if the validation fails 209 * 210 * @param theResource 211 * The resource to validate 212 * @throws ValidationFailureException 213 * If the validation fails 214 * @deprecated use {@link #validateWithResult(IBaseResource)} instead 215 */ 216 @Deprecated 217 public void validate(IResource theResource) throws ValidationFailureException { 218 219 applyDefaultValidators(); 220 221 ValidationResult validationResult = validateWithResult(theResource); 222 if (!validationResult.isSuccessful()) { 223 throw new ValidationFailureException(myContext, validationResult.toOperationOutcome()); 224 } 225 } 226 227 /** 228 * Validates a bundle instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results. This validation includes validation of all resources in the bundle. 229 * 230 * @param theBundle 231 * the bundle to validate 232 * @return the results of validation 233 * @since 0.7 234 */ 235 public ValidationResult validateWithResult(Bundle theBundle) { 236 Validate.notNull(theBundle, "theBundle must not be null"); 237 238 applyDefaultValidators(); 239 240 IValidationContext<Bundle> ctx = ValidationContext.forBundle(myContext, theBundle); 241 242 for (IValidatorModule next : myValidators) { 243 next.validateBundle(ctx); 244 } 245 246 return ctx.toResult(); 247 } 248 249 /** 250 * Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results. 251 * 252 * @param theResource 253 * the resource to validate 254 * @return the results of validation 255 * @since 0.7 256 */ 257 public ValidationResult validateWithResult(IBaseResource theResource) { 258 Validate.notNull(theResource, "theResource must not be null"); 259 260 applyDefaultValidators(); 261 262 IValidationContext<IBaseResource> ctx = ValidationContext.forResource(myContext, theResource); 263 264 for (IValidatorModule next : myValidators) { 265 next.validateResource(ctx); 266 } 267 268 return ctx.toResult(); 269 } 270 271 /** 272 * Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results. 273 * 274 * @param theResource 275 * the resource to validate 276 * @return the results of validation 277 * @since 1.1 278 */ 279 public ValidationResult validateWithResult(String theResource) { 280 Validate.notNull(theResource, "theResource must not be null"); 281 282 applyDefaultValidators(); 283 284 IValidationContext<IBaseResource> ctx = ValidationContext.forText(myContext, theResource); 285 286 for (IValidatorModule next : myValidators) { 287 next.validateResource(ctx); 288 } 289 290 return ctx.toResult(); 291 } 292 293}