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}