001package ca.uhn.fhir.validation;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2018 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.model.api.IResource;
029import ca.uhn.fhir.validation.schematron.SchematronProvider;
030
031/**
032 * Resource validator, which checks resources for compliance against various validation schemes (schemas, schematrons, profiles, etc.)
033 * 
034 * <p>
035 * To obtain a resource validator, call {@link FhirContext#newValidator()}
036 * </p>
037 * 
038 * <p>
039 * <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
040 * them with caution.
041 * </p>
042 */
043public class FhirValidator {
044
045        private static final String I18N_KEY_NO_PH_ERROR = FhirValidator.class.getName() + ".noPhError";
046
047        private static volatile Boolean ourPhPresentOnClasspath;
048        private final FhirContext myContext;
049        private List<IValidatorModule> myValidators = new ArrayList<IValidatorModule>();
050
051        /**
052         * Constructor (this should not be called directly, but rather {@link FhirContext#newValidator()} should be called to obtain an instance of {@link FhirValidator})
053         */
054        public FhirValidator(FhirContext theFhirContext) {
055                myContext = theFhirContext;
056
057                if (ourPhPresentOnClasspath == null) {
058                        ourPhPresentOnClasspath = SchematronProvider.isSchematronAvailable(theFhirContext);
059                }
060        }
061
062        private void addOrRemoveValidator(boolean theValidateAgainstStandardSchema, Class<? extends IValidatorModule> type, IValidatorModule theInstance) {
063                if (theValidateAgainstStandardSchema) {
064                        boolean found = haveValidatorOfType(type);
065                        if (!found) {
066                                registerValidatorModule(theInstance);
067                        }
068                } else {
069                        for (Iterator<IValidatorModule> iter = myValidators.iterator(); iter.hasNext();) {
070                                IValidatorModule next = iter.next();
071                                if (next.getClass().equals(type)) {
072                                        unregisterValidatorModule(next);
073                                }
074                        }
075                }
076        }
077
078        private boolean haveValidatorOfType(Class<? extends IValidatorModule> type) {
079                boolean found = false;
080                for (IValidatorModule next : myValidators) {
081                        if (next.getClass().equals(type)) {
082                                found = true;
083                        }
084                }
085                return found;
086        }
087
088        /**
089         * Should the validator validate the resource against the base schema (the schema provided with the FHIR distribution itself)
090         */
091        public synchronized boolean isValidateAgainstStandardSchema() {
092                return haveValidatorOfType(SchemaBaseValidator.class);
093        }
094
095        /**
096         * Should the validator validate the resource against the base schema (the schema provided with the FHIR distribution itself)
097         */
098        public synchronized boolean isValidateAgainstStandardSchematron() {
099                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}