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.context.support.ValidationSupportContext;
008import ca.uhn.fhir.validation.IInstanceValidatorModule;
009import ca.uhn.fhir.validation.IValidationContext;
010import org.apache.commons.lang3.Validate;
011import org.hl7.fhir.convertors.VersionConvertor_10_50;
012import org.hl7.fhir.exceptions.FHIRException;
013import org.hl7.fhir.exceptions.PathEngineException;
014import org.hl7.fhir.instance.model.api.IBaseResource;
015import org.hl7.fhir.r5.model.Base;
016import org.hl7.fhir.r5.model.Resource;
017import org.hl7.fhir.r5.model.TypeDetails;
018import org.hl7.fhir.r5.model.ValueSet;
019import org.hl7.fhir.r5.utils.FHIRPathEngine;
020import org.hl7.fhir.r5.utils.IResourceValidator;
021import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel;
022import org.hl7.fhir.utilities.validation.ValidationMessage;
023
024import javax.annotation.Nonnull;
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.List;
028
029@SuppressWarnings({"PackageAccessibility", "Duplicates"})
030public class FhirInstanceValidator extends BaseValidatorBridge implements IInstanceValidatorModule {
031
032        private boolean myAnyExtensionsAllowed = true;
033        private BestPracticeWarningLevel myBestPracticeWarningLevel;
034        private IValidationSupport myValidationSupport;
035        private boolean noTerminologyChecks = false;
036        private volatile VersionSpecificWorkerContextWrapper myWrappedWorkerContext;
037        private boolean errorForUnknownProfiles;
038        private boolean assumeValidRestReferences;
039        private List<String> myExtensionDomains = Collections.emptyList();
040        private IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher;
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        public List<String> getExtensionDomains() {
192                return myExtensionDomains;
193        }
194
195        @Override
196        protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) {
197                VersionSpecificWorkerContextWrapper wrappedWorkerContext = provideWorkerContext();
198
199                return new ValidatorWrapper()
200                        .setAnyExtensionsAllowed(isAnyExtensionsAllowed())
201                        .setBestPracticeWarningLevel(getBestPracticeWarningLevel())
202                        .setErrorForUnknownProfiles(isErrorForUnknownProfiles())
203                        .setExtensionDomains(getExtensionDomains())
204                        .setNoTerminologyChecks(isNoTerminologyChecks())
205                        .setValidatorResourceFetcher(getValidatorResourceFetcher())
206                        .setAssumeValidRestReferences(isAssumeValidRestReferences())
207                        .validate(wrappedWorkerContext, theValidationCtx);
208        }
209
210        @Nonnull
211        protected VersionSpecificWorkerContextWrapper provideWorkerContext() {
212                VersionSpecificWorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext;
213                if (wrappedWorkerContext == null) {
214                        VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter;
215
216                        switch (myValidationSupport.getFhirContext().getVersion().getVersion()) {
217                                case DSTU2:
218                                case DSTU2_HL7ORG: {
219                                        converter = new VersionSpecificWorkerContextWrapper.IVersionTypeConverter() {
220                                                @Override
221                                                public Resource toCanonical(IBaseResource theNonCanonical) {
222                                                        IBaseResource nonCanonical = theNonCanonical;
223                                                        Resource retVal = VersionConvertor_10_50.convertResource((org.hl7.fhir.dstu2.model.Resource) nonCanonical);
224                                                        if (nonCanonical instanceof org.hl7.fhir.dstu2.model.ValueSet) {
225                                                                org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) nonCanonical;
226                                                                if (valueSet.hasCodeSystem() && valueSet.getCodeSystem().hasSystem()) {
227                                                                        if (!valueSet.hasCompose()) {
228                                                                                ValueSet valueSetR5 = (ValueSet) retVal;
229                                                                                valueSetR5.getCompose().addInclude().setSystem(valueSet.getCodeSystem().getSystem());
230                                                                        }
231                                                                }
232                                                        }
233                                                        return retVal;
234                                                }
235
236                                                @Override
237                                                public IBaseResource fromCanonical(Resource theCanonical) {
238                                                        IBaseResource canonical = VersionConvertor_10_50.convertResource(theCanonical);
239                                                        return canonical;
240                                                }
241                                        };
242                                        break;
243                                }
244
245                                case DSTU2_1: {
246                                        converter = new VersionTypeConverterDstu21();
247                                        break;
248                                }
249
250                                case DSTU3: {
251                                        converter = new VersionTypeConverterDstu3();
252                                        break;
253                                }
254
255                                case R4: {
256                                        converter = new VersionTypeConverterR4();
257                                        break;
258                                }
259
260                                case R5: {
261                                        converter = VersionSpecificWorkerContextWrapper.IDENTITY_VERSION_TYPE_CONVERTER;
262                                        break;
263                                }
264
265                                default:
266                                        throw new IllegalStateException();
267                        }
268
269                        wrappedWorkerContext = new VersionSpecificWorkerContextWrapper(new ValidationSupportContext(myValidationSupport), converter);
270                }
271                myWrappedWorkerContext = wrappedWorkerContext;
272                return wrappedWorkerContext;
273        }
274
275        public IResourceValidator.IValidatorResourceFetcher getValidatorResourceFetcher() {
276                return validatorResourceFetcher;
277        }
278
279        public void setValidatorResourceFetcher(IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher) {
280                this.validatorResourceFetcher = validatorResourceFetcher;
281        }
282
283        public boolean isAssumeValidRestReferences() {
284                return assumeValidRestReferences;
285        }
286
287        public void setAssumeValidRestReferences(boolean assumeValidRestReferences) {
288                this.assumeValidRestReferences = assumeValidRestReferences;
289        }
290
291        /**
292         * Clear any cached data held by the validator or any of its internal stores. This is mostly intended
293         * for unit tests, but could be used for production uses too.
294         */
295        public void invalidateCaches() {
296                myValidationSupport.invalidateCaches();
297                if (myWrappedWorkerContext != null) {
298                        myWrappedWorkerContext.invalidateCaches();
299                }
300        }
301
302
303        public static class NullEvaluationContext implements FHIRPathEngine.IEvaluationContext {
304                @Override
305                public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
306                        return null;
307                }
308
309                @Override
310                public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
311                        return null;
312                }
313
314                @Override
315                public boolean log(String argument, List<Base> focus) {
316                        return false;
317                }
318
319                @Override
320                public FunctionDetails resolveFunction(String functionName) {
321                        return null;
322                }
323
324                @Override
325                public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException {
326                        return null;
327                }
328
329                @Override
330                public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
331                        return null;
332                }
333
334                @Override
335                public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException {
336                        return null;
337                }
338
339                @Override
340                public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException {
341                        return false;
342                }
343
344                @Override
345                public ValueSet resolveValueSet(Object appContext, String url) {
346                        return null;
347                }
348        }
349
350
351}