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