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