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