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}