001package ca.uhn.fhir.validation; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2020 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 * @return Returns a reference to <code>this</code> for easy method chaining. 113 */ 114 public synchronized FhirValidator 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 return this; 122 } 123 124 /** 125 * Should the validator validate the resource against the base schema (the schema provided with the FHIR distribution itself) 126 * 127 * @return Returns a referens to <code>this<code> for method chaining 128 */ 129 public synchronized FhirValidator setValidateAgainstStandardSchema(boolean theValidateAgainstStandardSchema) { 130 addOrRemoveValidator(theValidateAgainstStandardSchema, SchemaBaseValidator.class, new SchemaBaseValidator(myContext)); 131 return this; 132 } 133 134 /** 135 * Should the validator validate the resource against the base schematron (the schematron provided with the FHIR distribution itself) 136 * 137 * @return Returns a referens to <code>this<code> for method chaining 138 */ 139 public synchronized FhirValidator setValidateAgainstStandardSchematron(boolean theValidateAgainstStandardSchematron) { 140 if (theValidateAgainstStandardSchematron && !ourPhPresentOnClasspath) { 141 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_KEY_NO_PH_ERROR)); 142 } 143 if (!theValidateAgainstStandardSchematron && !ourPhPresentOnClasspath) { 144 return this; 145 } 146 Class<? extends IValidatorModule> cls = SchematronProvider.getSchematronValidatorClass(); 147 IValidatorModule instance = SchematronProvider.getSchematronValidatorInstance(myContext); 148 addOrRemoveValidator(theValidateAgainstStandardSchematron, cls, instance); 149 return this; 150 } 151 152 /** 153 * Removes a validator module from this validator. You may register as many modules as you like, and remove them at any time. 154 * 155 * @param theValidator 156 * The validator module. Must not be null. 157 */ 158 public synchronized void unregisterValidatorModule(IValidatorModule theValidator) { 159 Validate.notNull(theValidator, "theValidator must not be null"); 160 ArrayList<IValidatorModule> newValidators = new ArrayList<IValidatorModule>(myValidators.size() + 1); 161 newValidators.addAll(myValidators); 162 newValidators.remove(theValidator); 163 164 myValidators = newValidators; 165 } 166 167 168 private void applyDefaultValidators() { 169 if (myValidators.isEmpty()) { 170 setValidateAgainstStandardSchema(true); 171 if (ourPhPresentOnClasspath) { 172 setValidateAgainstStandardSchematron(true); 173 } 174 } 175 } 176 177 178 179 /** 180 * Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results. 181 * 182 * @param theResource 183 * the resource to validate 184 * @return the results of validation 185 * @since 0.7 186 */ 187 public ValidationResult validateWithResult(IBaseResource theResource) { 188 return validateWithResult(theResource, null); 189 } 190 191 /** 192 * Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results. 193 * 194 * @param theResource 195 * the resource to validate 196 * @return the results of validation 197 * @since 1.1 198 */ 199 public ValidationResult validateWithResult(String theResource) { 200 return validateWithResult(theResource, null); 201 } 202 203 /** 204 * Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results. 205 * 206 * @param theResource 207 * the resource to validate 208 * @param theOptions 209 * 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 return ctx.toResult(); 225 } 226 227 /** 228 * Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results. 229 * 230 * @param theResource 231 * the resource to validate 232 * @param theOptions 233 * Optionally provides options to the validator 234 * @return the results of validation 235 * @since 4.0.0 236 */ 237 public ValidationResult validateWithResult(String theResource, ValidationOptions theOptions) { 238 Validate.notNull(theResource, "theResource must not be null"); 239 240 applyDefaultValidators(); 241 242 IValidationContext<IBaseResource> ctx = ValidationContext.forText(myContext, theResource, theOptions); 243 244 for (IValidatorModule next : myValidators) { 245 next.validateResource(ctx); 246 } 247 248 return ctx.toResult(); 249 } 250}