001package org.hl7.fhir.common.hapi.validation.validator;
002
003import ca.uhn.fhir.context.FhirContext;
004import ca.uhn.fhir.context.FhirVersionEnum;
005import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
006import ca.uhn.fhir.context.support.IValidationSupport;
007import ca.uhn.fhir.validation.IInstanceValidatorModule;
008import ca.uhn.fhir.validation.IValidationContext;
009import org.apache.commons.lang3.Validate;
010import org.hl7.fhir.exceptions.FHIRException;
011import org.hl7.fhir.exceptions.PathEngineException;
012import org.hl7.fhir.r5.model.Base;
013import org.hl7.fhir.r5.model.TypeDetails;
014import org.hl7.fhir.r5.model.ValueSet;
015import org.hl7.fhir.r5.utils.FHIRPathEngine;
016import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
017import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
018import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
019import org.hl7.fhir.utilities.validation.ValidationMessage;
020
021import javax.annotation.Nonnull;
022import java.util.Arrays;
023import java.util.Collections;
024import java.util.List;
025
026@SuppressWarnings({"PackageAccessibility", "Duplicates"})
027public class FhirInstanceValidator extends BaseValidatorBridge implements IInstanceValidatorModule {
028
029        private boolean myAnyExtensionsAllowed = true;
030        private BestPracticeWarningLevel myBestPracticeWarningLevel;
031        private IValidationSupport myValidationSupport;
032        private boolean noTerminologyChecks = false;
033        private boolean noExtensibleWarnings = false;
034        private boolean noBindingMsgSuppressed = false;
035        private volatile VersionSpecificWorkerContextWrapper myWrappedWorkerContext;
036        private boolean errorForUnknownProfiles = true;
037        private boolean assumeValidRestReferences;
038        private List<String> myExtensionDomains = Collections.emptyList();
039        private IValidatorResourceFetcher validatorResourceFetcher;
040        private IValidationPolicyAdvisor validatorPolicyAdvisor;
041
042        /**
043         * Constructor
044         * <p>
045         * Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support}
046         */
047        public FhirInstanceValidator(FhirContext theContext) {
048                this(theContext.getValidationSupport());
049        }
050
051        /**
052         * Constructor which uses the given validation support
053         *
054         * @param theValidationSupport The validation support
055         */
056        public FhirInstanceValidator(IValidationSupport theValidationSupport) {
057                if (theValidationSupport.getFhirContext().getVersion().getVersion() == FhirVersionEnum.DSTU2) {
058                        myValidationSupport = new HapiToHl7OrgDstu2ValidatingSupportWrapper(theValidationSupport);
059                } else {
060                        myValidationSupport = theValidationSupport;
061                }
062        }
063
064        /**
065         * Every element in a resource or data type includes an optional <it>extension</it> child element
066         * which is identified by it's {@code url attribute}. There exists a number of predefined
067         * extension urls or extension domains:<ul>
068         * <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
069         * <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
070         * </ul>
071         * It is possible to extend this list of known extension by defining custom extensions:
072         * Any url which starts which one of the elements in the list of custom extension domains is
073         * considered as known.
074         * <p>
075         * Any unknown extension domain will result in an information message when validating a resource.
076         * </p>
077         */
078        public FhirInstanceValidator setCustomExtensionDomains(List<String> extensionDomains) {
079                this.myExtensionDomains = extensionDomains;
080                return this;
081        }
082
083        /**
084         * Every element in a resource or data type includes an optional <it>extension</it> child element
085         * which is identified by it's {@code url attribute}. There exists a number of predefined
086         * extension urls or extension domains:<ul>
087         * <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
088         * <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
089         * </ul>
090         * It is possible to extend this list of known extension by defining custom extensions:
091         * Any url which starts which one of the elements in the list of custom extension domains is
092         * considered as known.
093         * <p>
094         * Any unknown extension domain will result in an information message when validating a resource.
095         * </p>
096         */
097        public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) {
098                this.myExtensionDomains = Arrays.asList(extensionDomains);
099                return this;
100        }
101
102        /**
103         * Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}).
104         * <p>
105         * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is
106         * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be
107         * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
108         * guielines will be ignored.
109         * </p>
110         *
111         * @see #setBestPracticeWarningLevel(BestPracticeWarningLevel)
112         */
113        public BestPracticeWarningLevel getBestPracticeWarningLevel() {
114                return myBestPracticeWarningLevel;
115        }
116
117        /**
118         * Sets the "best practice warning level". When validating, any deviations from best practices will be reported at
119         * this level.
120         * <p>
121         * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is
122         * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be
123         * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
124         * guielines will be ignored.
125         * </p>
126         *
127         * @param theBestPracticeWarningLevel The level, must not be <code>null</code>
128         */
129        public void setBestPracticeWarningLevel(BestPracticeWarningLevel theBestPracticeWarningLevel) {
130                Validate.notNull(theBestPracticeWarningLevel);
131                myBestPracticeWarningLevel = theBestPracticeWarningLevel;
132        }
133
134        /**
135         * Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
136         * DefaultProfileValidationSupport if the no-arguments constructor for this object was used.
137         */
138        public IValidationSupport getValidationSupport() {
139                return myValidationSupport;
140        }
141
142        /**
143         * Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
144         * DefaultProfileValidationSupport if the no-arguments constructor for this object was used.
145         */
146        public void setValidationSupport(IValidationSupport theValidationSupport) {
147                myValidationSupport = theValidationSupport;
148                myWrappedWorkerContext = null;
149        }
150
151        /**
152         * If set to {@literal true} (default is true) extensions which are not known to the
153         * validator (e.g. because they have not been explicitly declared in a profile) will
154         * be validated but will not cause an error.
155         */
156        public boolean isAnyExtensionsAllowed() {
157                return myAnyExtensionsAllowed;
158        }
159
160        /**
161         * If set to {@literal true} (default is true) extensions which are not known to the
162         * validator (e.g. because they have not been explicitly declared in a profile) will
163         * be validated but will not cause an error.
164         */
165        public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) {
166                myAnyExtensionsAllowed = theAnyExtensionsAllowed;
167        }
168
169        public boolean isErrorForUnknownProfiles() {
170                return errorForUnknownProfiles;
171        }
172
173        public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) {
174                this.errorForUnknownProfiles = errorForUnknownProfiles;
175        }
176
177        /**
178         * If set to {@literal true} (default is false) the valueSet will not be validate
179         */
180        public boolean isNoTerminologyChecks() {
181                return noTerminologyChecks;
182        }
183
184        /**
185         * If set to {@literal true} (default is false) the valueSet will not be validate
186         */
187        public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) {
188                noTerminologyChecks = theNoTerminologyChecks;
189        }
190
191        /**
192         * If set to {@literal true} (default is false) no extensible warnings suppressed
193         */
194        public boolean isNoExtensibleWarnings() {
195                return noExtensibleWarnings;
196        }
197
198        /**
199         * If set to {@literal true} (default is false) no extensible warnings is suppressed
200         */
201        public void setNoExtensibleWarnings(final boolean theNoExtensibleWarnings) {
202                noExtensibleWarnings = theNoExtensibleWarnings;
203        }
204
205        /**
206         * If set to {@literal true} (default is false) no binding message is suppressed
207         */
208        public boolean isNoBindingMsgSuppressed() {
209                return noBindingMsgSuppressed;
210        }
211
212        /**
213         * If set to {@literal true} (default is false) no binding message is suppressed
214         */
215        public void setNoBindingMsgSuppressed(final boolean theNoBindingMsgSuppressed) {
216                noBindingMsgSuppressed = theNoBindingMsgSuppressed;
217        }
218
219        public List<String> getExtensionDomains() {
220                return myExtensionDomains;
221        }
222
223        @Override
224        protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) {
225                VersionSpecificWorkerContextWrapper wrappedWorkerContext = provideWorkerContext();
226
227                return new ValidatorWrapper()
228                        .setAnyExtensionsAllowed(isAnyExtensionsAllowed())
229                        .setBestPracticeWarningLevel(getBestPracticeWarningLevel())
230                        .setErrorForUnknownProfiles(isErrorForUnknownProfiles())
231                        .setExtensionDomains(getExtensionDomains())
232                        .setValidatorResourceFetcher(validatorResourceFetcher)
233                        .setValidationPolicyAdvisor(validatorPolicyAdvisor)
234                        .setNoTerminologyChecks(isNoTerminologyChecks())
235                        .setNoExtensibleWarnings(isNoExtensibleWarnings())
236                        .setNoBindingMsgSuppressed(isNoBindingMsgSuppressed())
237                        .setValidatorResourceFetcher(getValidatorResourceFetcher())
238                        .setAssumeValidRestReferences(isAssumeValidRestReferences())
239                        .validate(wrappedWorkerContext, theValidationCtx);
240        }
241
242        @Nonnull
243        protected VersionSpecificWorkerContextWrapper provideWorkerContext() {
244                VersionSpecificWorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext;
245                if (wrappedWorkerContext == null) {
246                        wrappedWorkerContext = VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(myValidationSupport);
247                }
248                myWrappedWorkerContext = wrappedWorkerContext;
249                return wrappedWorkerContext;
250        }
251
252        public IValidationPolicyAdvisor getValidatorPolicyAdvisor() {
253                return validatorPolicyAdvisor;
254        }
255
256        public void setValidatorPolicyAdvisor(IValidationPolicyAdvisor validatorPolicyAdvisor) {
257                this.validatorPolicyAdvisor = validatorPolicyAdvisor;
258        }
259
260        public IValidatorResourceFetcher getValidatorResourceFetcher() {
261                return validatorResourceFetcher;
262        }
263
264        public void setValidatorResourceFetcher(IValidatorResourceFetcher validatorResourceFetcher) {
265                this.validatorResourceFetcher = validatorResourceFetcher;
266        }
267
268        public boolean isAssumeValidRestReferences() {
269                return assumeValidRestReferences;
270        }
271
272        public void setAssumeValidRestReferences(boolean assumeValidRestReferences) {
273                this.assumeValidRestReferences = assumeValidRestReferences;
274        }
275
276        /**
277         * Clear any cached data held by the validator or any of its internal stores. This is mostly intended
278         * for unit tests, but could be used for production uses too.
279         */
280        public void invalidateCaches() {
281                myValidationSupport.invalidateCaches();
282                if (myWrappedWorkerContext != null) {
283                        myWrappedWorkerContext.invalidateCaches();
284                }
285        }
286
287
288        public static class NullEvaluationContext implements FHIRPathEngine.IEvaluationContext {
289
290                @Override
291                public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
292                        return Collections.emptyList();
293                }
294
295                @Override
296                public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
297                        return null;
298                }
299
300                @Override
301                public boolean log(String argument, List<Base> focus) {
302                        return false;
303                }
304
305                @Override
306                public FunctionDetails resolveFunction(String functionName) {
307                        return null;
308                }
309
310                @Override
311                public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException {
312                        return null;
313                }
314
315                @Override
316                public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
317                        return null;
318                }
319
320                @Override
321                public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException {
322                        return null;
323                }
324
325                @Override
326                public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException {
327                        return false;
328                }
329
330                @Override
331                public ValueSet resolveValueSet(Object appContext, String url) {
332                        return null;
333                }
334        }
335
336
337}