001package ca.uhn.fhir.validation;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2017 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_PHLOC_ERROR = FhirValidator.class.getName() + ".noPhlocError";
046
047        private static volatile Boolean ourPhlocPresentOnClasspath;
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 (ourPhlocPresentOnClasspath == null) {
058                        ourPhlocPresentOnClasspath = 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 (!ourPhlocPresentOnClasspath) {
100                        return false;   // No need to ask since we dont have Phloc. Also Class.forname will complain
101                                                        // about missing phloc import.
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 && !ourPhlocPresentOnClasspath) {
139                        throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_KEY_NO_PHLOC_ERROR));
140                }
141                Class<? extends IValidatorModule> cls = SchematronProvider.getSchematronValidatorClass();
142                IValidatorModule instance = SchematronProvider.getSchematronValidatorInstance(myContext);
143                addOrRemoveValidator(theValidateAgainstStandardSchematron, cls, instance);
144                return this;
145        }
146
147        /**
148         * Removes a validator module from this validator. You may register as many modules as you like, and remove them at any time.
149         * 
150         * @param theValidator
151         *           The validator module. Must not be null.
152         */
153        public synchronized void unregisterValidatorModule(IValidatorModule theValidator) {
154                Validate.notNull(theValidator, "theValidator must not be null");
155                ArrayList<IValidatorModule> newValidators = new ArrayList<IValidatorModule>(myValidators.size() + 1);
156                newValidators.addAll(myValidators);
157                newValidators.remove(theValidator);
158
159                myValidators = newValidators;
160        }
161
162
163        private void applyDefaultValidators() {
164                if (myValidators.isEmpty()) {
165                        setValidateAgainstStandardSchema(true);
166                        if (ourPhlocPresentOnClasspath) {
167                                setValidateAgainstStandardSchematron(true);
168                        }
169                }
170        }
171
172        /**
173         * Validates a resource instance, throwing a {@link ValidationFailureException} if the validation fails
174         * 
175         * @param theResource
176         *           The resource to validate
177         * @throws ValidationFailureException
178         *            If the validation fails
179         * @deprecated use {@link #validateWithResult(IBaseResource)} instead
180         */
181        @Deprecated
182        public void validate(IResource theResource) throws ValidationFailureException {
183                
184                applyDefaultValidators();
185                
186                ValidationResult validationResult = validateWithResult(theResource);
187                if (!validationResult.isSuccessful()) {
188                        throw new ValidationFailureException(myContext, validationResult.toOperationOutcome());
189                }
190        }
191
192
193        /**
194         * Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results.
195         *
196         * @param theResource
197         *           the resource to validate
198         * @return the results of validation
199         * @since 0.7
200         */
201        public ValidationResult validateWithResult(IBaseResource theResource) {
202                Validate.notNull(theResource, "theResource must not be null");
203                
204                applyDefaultValidators();
205                
206                IValidationContext<IBaseResource> ctx = ValidationContext.forResource(myContext, theResource);
207
208                for (IValidatorModule next : myValidators) {
209                        next.validateResource(ctx);
210                }
211
212                return ctx.toResult();
213        }
214
215        /**
216         * Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results.
217         *
218         * @param theResource
219         *           the resource to validate
220         * @return the results of validation
221         * @since 1.1
222         */
223        public ValidationResult validateWithResult(String theResource) {
224                Validate.notNull(theResource, "theResource must not be null");
225                
226                applyDefaultValidators();
227                
228                IValidationContext<IBaseResource> ctx = ValidationContext.forText(myContext, theResource);
229
230                for (IValidatorModule next : myValidators) {
231                        next.validateResource(ctx);
232                }
233
234                return ctx.toResult();
235        }
236
237}