View Javadoc
1   package ca.uhn.fhir.validation;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  import java.util.*;
23  
24  import org.apache.commons.lang3.Validate;
25  import org.hl7.fhir.instance.model.api.IBaseResource;
26  
27  import ca.uhn.fhir.context.FhirContext;
28  import ca.uhn.fhir.model.api.IResource;
29  import ca.uhn.fhir.validation.schematron.SchematronProvider;
30  
31  /**
32   * Resource validator, which checks resources for compliance against various validation schemes (schemas, schematrons, profiles, etc.)
33   * 
34   * <p>
35   * To obtain a resource validator, call {@link FhirContext#newValidator()}
36   * </p>
37   * 
38   * <p>
39   * <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
40   * them with caution.
41   * </p>
42   */
43  public class FhirValidator {
44  
45  	private static final String I18N_KEY_NO_PH_ERROR = FhirValidator.class.getName() + ".noPhError";
46  
47  	private static volatile Boolean ourPhPresentOnClasspath;
48  	private final FhirContext myContext;
49  	private List<IValidatorModule> myValidators = new ArrayList<IValidatorModule>();
50  
51  	/**
52  	 * Constructor (this should not be called directly, but rather {@link FhirContext#newValidator()} should be called to obtain an instance of {@link FhirValidator})
53  	 */
54  	public FhirValidator(FhirContext theFhirContext) {
55  		myContext = theFhirContext;
56  
57  		if (ourPhPresentOnClasspath == null) {
58  			ourPhPresentOnClasspath = SchematronProvider.isSchematronAvailable(theFhirContext);
59  		}
60  	}
61  
62  	private void addOrRemoveValidator(boolean theValidateAgainstStandardSchema, Class<? extends IValidatorModule> type, IValidatorModule theInstance) {
63  		if (theValidateAgainstStandardSchema) {
64  			boolean found = haveValidatorOfType(type);
65  			if (!found) {
66  				registerValidatorModule(theInstance);
67  			}
68  		} else {
69  			for (Iterator<IValidatorModule> iter = myValidators.iterator(); iter.hasNext();) {
70  				IValidatorModule next = iter.next();
71  				if (next.getClass().equals(type)) {
72  					unregisterValidatorModule(next);
73  				}
74  			}
75  		}
76  	}
77  
78  	private boolean haveValidatorOfType(Class<? extends IValidatorModule> type) {
79  		boolean found = false;
80  		for (IValidatorModule next : myValidators) {
81  			if (next.getClass().equals(type)) {
82  				found = true;
83  			}
84  		}
85  		return found;
86  	}
87  
88  	/**
89  	 * Should the validator validate the resource against the base schema (the schema provided with the FHIR distribution itself)
90  	 */
91  	public synchronized boolean isValidateAgainstStandardSchema() {
92  		return haveValidatorOfType(SchemaBaseValidator.class);
93  	}
94  
95  	/**
96  	 * Should the validator validate the resource against the base schema (the schema provided with the FHIR distribution itself)
97  	 */
98  	public synchronized boolean isValidateAgainstStandardSchematron() {
99  		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 }