001package ca.uhn.fhir.validation; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2018 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 */ 022import java.util.*; 023 024import org.apache.commons.lang3.Validate; 025import org.hl7.fhir.instance.model.api.IBaseResource; 026 027import ca.uhn.fhir.context.FhirContext; 028import ca.uhn.fhir.model.api.IResource; 029import ca.uhn.fhir.validation.schematron.SchematronProvider; 030 031/** 032 * Resource validator, which checks resources for compliance against various validation schemes (schemas, schematrons, profiles, etc.) 033 * 034 * <p> 035 * To obtain a resource validator, call {@link FhirContext#newValidator()} 036 * </p> 037 * 038 * <p> 039 * <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 040 * them with caution. 041 * </p> 042 */ 043public class FhirValidator { 044 045 private static final String I18N_KEY_NO_PH_ERROR = FhirValidator.class.getName() + ".noPhError"; 046 047 private static volatile Boolean ourPhPresentOnClasspath; 048 private final FhirContext myContext; 049 private List<IValidatorModule> myValidators = new ArrayList<IValidatorModule>(); 050 051 /** 052 * Constructor (this should not be called directly, but rather {@link FhirContext#newValidator()} should be called to obtain an instance of {@link FhirValidator}) 053 */ 054 public FhirValidator(FhirContext theFhirContext) { 055 myContext = theFhirContext; 056 057 if (ourPhPresentOnClasspath == null) { 058 ourPhPresentOnClasspath = SchematronProvider.isSchematronAvailable(theFhirContext); 059 } 060 } 061 062 private void addOrRemoveValidator(boolean theValidateAgainstStandardSchema, Class<? extends IValidatorModule> type, IValidatorModule theInstance) { 063 if (theValidateAgainstStandardSchema) { 064 boolean found = haveValidatorOfType(type); 065 if (!found) { 066 registerValidatorModule(theInstance); 067 } 068 } else { 069 for (Iterator<IValidatorModule> iter = myValidators.iterator(); iter.hasNext();) { 070 IValidatorModule next = iter.next(); 071 if (next.getClass().equals(type)) { 072 unregisterValidatorModule(next); 073 } 074 } 075 } 076 } 077 078 private boolean haveValidatorOfType(Class<? extends IValidatorModule> type) { 079 boolean found = false; 080 for (IValidatorModule next : myValidators) { 081 if (next.getClass().equals(type)) { 082 found = true; 083 } 084 } 085 return found; 086 } 087 088 /** 089 * Should the validator validate the resource against the base schema (the schema provided with the FHIR distribution itself) 090 */ 091 public synchronized boolean isValidateAgainstStandardSchema() { 092 return haveValidatorOfType(SchemaBaseValidator.class); 093 } 094 095 /** 096 * Should the validator validate the resource against the base schema (the schema provided with the FHIR distribution itself) 097 */ 098 public synchronized boolean isValidateAgainstStandardSchematron() { 099 if (!ourPhPresentOnClasspath) { 100 // No need to ask since we dont have Ph-Schematron. Also Class.forname will complain 101 // about missing ph-schematron import. 102 return false; 103 } 104 Class<? extends IValidatorModule> cls = SchematronProvider.getSchematronValidatorClass(); 105 return haveValidatorOfType(cls); 106 } 107 108 /** 109 * Add a new validator module to this validator. You may register as many modules as you like at any time. 110 * 111 * @param theValidator 112 * The validator module. Must not be null. 113 */ 114 public synchronized void registerValidatorModule(IValidatorModule theValidator) { 115 Validate.notNull(theValidator, "theValidator must not be null"); 116 ArrayList<IValidatorModule> newValidators = new ArrayList<IValidatorModule>(myValidators.size() + 1); 117 newValidators.addAll(myValidators); 118 newValidators.add(theValidator); 119 120 myValidators = newValidators; 121 } 122 123 /** 124 * Should the validator validate the resource against the base schema (the schema provided with the FHIR distribution itself) 125 * 126 * @return Returns a referens to <code>this<code> for method chaining 127 */ 128 public synchronized FhirValidator setValidateAgainstStandardSchema(boolean theValidateAgainstStandardSchema) { 129 addOrRemoveValidator(theValidateAgainstStandardSchema, SchemaBaseValidator.class, new SchemaBaseValidator(myContext)); 130 return this; 131 } 132 133 /** 134 * Should the validator validate the resource against the base schematron (the schematron provided with the FHIR distribution itself) 135 * 136 * @return Returns a referens to <code>this<code> for method chaining 137 */ 138 public synchronized FhirValidator setValidateAgainstStandardSchematron(boolean theValidateAgainstStandardSchematron) { 139 if (theValidateAgainstStandardSchematron && !ourPhPresentOnClasspath) { 140 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_KEY_NO_PH_ERROR)); 141 } 142 if (!theValidateAgainstStandardSchematron && !ourPhPresentOnClasspath) { 143 return this; 144 } 145 Class<? extends IValidatorModule> cls = SchematronProvider.getSchematronValidatorClass(); 146 IValidatorModule instance = SchematronProvider.getSchematronValidatorInstance(myContext); 147 addOrRemoveValidator(theValidateAgainstStandardSchematron, cls, instance); 148 return this; 149 } 150 151 /** 152 * Removes a validator module from this validator. You may register as many modules as you like, and remove them at any time. 153 * 154 * @param theValidator 155 * The validator module. Must not be null. 156 */ 157 public synchronized void unregisterValidatorModule(IValidatorModule theValidator) { 158 Validate.notNull(theValidator, "theValidator must not be null"); 159 ArrayList<IValidatorModule> newValidators = new ArrayList<IValidatorModule>(myValidators.size() + 1); 160 newValidators.addAll(myValidators); 161 newValidators.remove(theValidator); 162 163 myValidators = newValidators; 164 } 165 166 167 private void applyDefaultValidators() { 168 if (myValidators.isEmpty()) { 169 setValidateAgainstStandardSchema(true); 170 if (ourPhPresentOnClasspath) { 171 setValidateAgainstStandardSchematron(true); 172 } 173 } 174 } 175 176 /** 177 * Validates a resource instance, throwing a {@link ValidationFailureException} if the validation fails 178 * 179 * @param theResource 180 * The resource to validate 181 * @throws ValidationFailureException 182 * If the validation fails 183 * @deprecated use {@link #validateWithResult(IBaseResource)} instead 184 */ 185 @Deprecated 186 public void validate(IResource theResource) throws ValidationFailureException { 187 188 applyDefaultValidators(); 189 190 ValidationResult validationResult = validateWithResult(theResource); 191 if (!validationResult.isSuccessful()) { 192 throw new ValidationFailureException(myContext, validationResult.toOperationOutcome()); 193 } 194 } 195 196 197 /** 198 * Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results. 199 * 200 * @param theResource 201 * the resource to validate 202 * @return the results of validation 203 * @since 0.7 204 */ 205 public ValidationResult validateWithResult(IBaseResource theResource) { 206 Validate.notNull(theResource, "theResource must not be null"); 207 208 applyDefaultValidators(); 209 210 IValidationContext<IBaseResource> ctx = ValidationContext.forResource(myContext, theResource); 211 212 for (IValidatorModule next : myValidators) { 213 next.validateResource(ctx); 214 } 215 216 return ctx.toResult(); 217 } 218 219 /** 220 * Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results. 221 * 222 * @param theResource 223 * the resource to validate 224 * @return the results of validation 225 * @since 1.1 226 */ 227 public ValidationResult validateWithResult(String theResource) { 228 Validate.notNull(theResource, "theResource must not be null"); 229 230 applyDefaultValidators(); 231 232 IValidationContext<IBaseResource> ctx = ValidationContext.forText(myContext, theResource); 233 234 for (IValidatorModule next : myValidators) { 235 next.validateResource(ctx); 236 } 237 238 return ctx.toResult(); 239 } 240 241}