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