001package ca.uhn.fhir.context;
002
003import ca.uhn.fhir.context.api.AddProfileTagEnum;
004import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
005import ca.uhn.fhir.context.support.IValidationSupport;
006import ca.uhn.fhir.fhirpath.IFhirPath;
007import ca.uhn.fhir.i18n.HapiLocalizer;
008import ca.uhn.fhir.model.api.IElement;
009import ca.uhn.fhir.model.api.IFhirVersion;
010import ca.uhn.fhir.model.api.IResource;
011import ca.uhn.fhir.model.view.ViewGenerator;
012import ca.uhn.fhir.narrative.INarrativeGenerator;
013import ca.uhn.fhir.parser.DataFormatException;
014import ca.uhn.fhir.parser.IParser;
015import ca.uhn.fhir.parser.IParserErrorHandler;
016import ca.uhn.fhir.parser.JsonParser;
017import ca.uhn.fhir.parser.LenientErrorHandler;
018import ca.uhn.fhir.parser.RDFParser;
019import ca.uhn.fhir.parser.XmlParser;
020import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
021import ca.uhn.fhir.rest.client.api.IBasicClient;
022import ca.uhn.fhir.rest.client.api.IGenericClient;
023import ca.uhn.fhir.rest.client.api.IRestfulClient;
024import ca.uhn.fhir.rest.client.api.IRestfulClientFactory;
025import ca.uhn.fhir.util.FhirTerser;
026import ca.uhn.fhir.util.ReflectionUtil;
027import ca.uhn.fhir.util.VersionUtil;
028import ca.uhn.fhir.validation.FhirValidator;
029import org.apache.commons.lang3.Validate;
030import org.apache.commons.lang3.exception.ExceptionUtils;
031import org.apache.jena.riot.Lang;
032import org.hl7.fhir.instance.model.api.IBase;
033import org.hl7.fhir.instance.model.api.IBaseBundle;
034import org.hl7.fhir.instance.model.api.IBaseResource;
035import org.hl7.fhir.instance.model.api.IPrimitiveType;
036
037import javax.annotation.Nonnull;
038import javax.annotation.Nullable;
039import java.io.IOException;
040import java.io.InputStream;
041import java.lang.reflect.Method;
042import java.lang.reflect.Modifier;
043import java.util.ArrayList;
044import java.util.Arrays;
045import java.util.Collection;
046import java.util.Collections;
047import java.util.EnumMap;
048import java.util.Enumeration;
049import java.util.HashMap;
050import java.util.HashSet;
051import java.util.List;
052import java.util.Map;
053import java.util.Map.Entry;
054import java.util.Properties;
055import java.util.Set;
056
057/*
058 * #%L
059 * HAPI FHIR - Core Library
060 * %%
061 * Copyright (C) 2014 - 2021 Smile CDR, Inc.
062 * %%
063 * Licensed under the Apache License, Version 2.0 (the "License");
064 * you may not use this file except in compliance with the License.
065 * You may obtain a copy of the License at
066 *
067 * http://www.apache.org/licenses/LICENSE-2.0
068 *
069 * Unless required by applicable law or agreed to in writing, software
070 * distributed under the License is distributed on an "AS IS" BASIS,
071 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
072 * See the License for the specific language governing permissions and
073 * limitations under the License.
074 * #L%
075 */
076
077/**
078 * The FHIR context is the central starting point for the use of the HAPI FHIR API. It should be created once, and then
079 * used as a factory for various other types of objects (parsers, clients, etc.).
080 *
081 * <p>
082 * Important usage notes:
083 * </p>
084 * <ul>
085 * <li>
086 * Thread safety: <b>This class is thread safe</b> and may be shared between multiple processing
087 * threads, except for the {@link #registerCustomType} and {@link #registerCustomTypes} methods.
088 * </li>
089 * <li>
090 * Performance: <b>This class is expensive</b> to create, as it scans every resource class it needs to parse or encode
091 * to build up an internal model of those classes. For that reason, you should try to create one FhirContext instance
092 * which remains for the life of your application and reuse that instance. Note that it will not cause problems to
093 * create multiple instances (ie. resources originating from one FhirContext may be passed to parsers originating from
094 * another) but you will incur a performance penalty if a new FhirContext is created for every message you parse/encode.
095 * </li>
096 * </ul>
097 */
098public class FhirContext {
099
100        private static final List<Class<? extends IBaseResource>> EMPTY_LIST = Collections.emptyList();
101        private static final Map<FhirVersionEnum, FhirContext> ourStaticContexts = Collections.synchronizedMap(new EnumMap<>(FhirVersionEnum.class));
102        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContext.class);
103        private final IFhirVersion myVersion;
104        private final Map<String, Class<? extends IBaseResource>> myDefaultTypeForProfile = new HashMap<>();
105        private final Set<PerformanceOptionsEnum> myPerformanceOptions = new HashSet<>();
106        private final Collection<Class<? extends IBaseResource>> myResourceTypesToScan;
107        private AddProfileTagEnum myAddProfileTagWhenEncoding = AddProfileTagEnum.ONLY_FOR_CUSTOM;
108        private volatile Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition = Collections.emptyMap();
109        private ArrayList<Class<? extends IBase>> myCustomTypes;
110        private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap();
111        private volatile boolean myInitialized;
112        private volatile boolean myInitializing = false;
113        private HapiLocalizer myLocalizer = new HapiLocalizer();
114        private volatile Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinition = Collections.emptyMap();
115        private volatile Map<String, RuntimeResourceDefinition> myNameToResourceDefinition = Collections.emptyMap();
116        private volatile Map<String, Class<? extends IBaseResource>> myNameToResourceType;
117        private volatile INarrativeGenerator myNarrativeGenerator;
118        private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler();
119        private ParserOptions myParserOptions = new ParserOptions();
120        private volatile IRestfulClientFactory myRestfulClientFactory;
121        private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
122        private IValidationSupport myValidationSupport;
123        private Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> myVersionToNameToResourceType = Collections.emptyMap();
124        private volatile Set<String> myResourceNames;
125        private volatile Boolean myFormatXmlSupported;
126        private volatile Boolean myFormatJsonSupported;
127        private volatile Boolean myFormatRdfSupported;
128
129        /**
130         * @deprecated It is recommended that you use one of the static initializer methods instead
131         * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()}
132         */
133        @Deprecated
134        public FhirContext() {
135                this(EMPTY_LIST);
136        }
137
138        /**
139         * @deprecated It is recommended that you use one of the static initializer methods instead
140         * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()}
141         */
142        @Deprecated
143        public FhirContext(final Class<? extends IBaseResource> theResourceType) {
144                this(toCollection(theResourceType));
145        }
146
147        /**
148         * @deprecated It is recommended that you use one of the static initializer methods instead
149         * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()}
150         */
151        @Deprecated
152        public FhirContext(final Class<?>... theResourceTypes) {
153                this(toCollection(theResourceTypes));
154        }
155
156        /**
157         * @deprecated It is recommended that you use one of the static initializer methods instead
158         * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()}
159         */
160        @Deprecated
161        public FhirContext(final Collection<Class<? extends IBaseResource>> theResourceTypes) {
162                this(null, theResourceTypes);
163        }
164
165        /**
166         * In most cases it is recommended that you use one of the static initializer methods instead
167         * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()}, but
168         * this method can also be used if you wish to supply the version programmatically.
169         */
170        public FhirContext(final FhirVersionEnum theVersion) {
171                this(theVersion, null);
172        }
173
174        private FhirContext(final FhirVersionEnum theVersion, final Collection<Class<? extends IBaseResource>> theResourceTypes) {
175                VersionUtil.getVersion();
176
177                if (theVersion != null) {
178                        if (!theVersion.isPresentOnClasspath()) {
179                                throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructuresForSpecifiedVersion", theVersion.name()));
180                        }
181                        myVersion = theVersion.getVersionImplementation();
182                } else if (FhirVersionEnum.DSTU2.isPresentOnClasspath()) {
183                        myVersion = FhirVersionEnum.DSTU2.getVersionImplementation();
184                } else if (FhirVersionEnum.DSTU2_HL7ORG.isPresentOnClasspath()) {
185                        myVersion = FhirVersionEnum.DSTU2_HL7ORG.getVersionImplementation();
186                } else if (FhirVersionEnum.DSTU2_1.isPresentOnClasspath()) {
187                        myVersion = FhirVersionEnum.DSTU2_1.getVersionImplementation();
188                } else if (FhirVersionEnum.DSTU3.isPresentOnClasspath()) {
189                        myVersion = FhirVersionEnum.DSTU3.getVersionImplementation();
190                } else if (FhirVersionEnum.R4.isPresentOnClasspath()) {
191                        myVersion = FhirVersionEnum.R4.getVersionImplementation();
192                } else {
193                        throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructures"));
194                }
195
196                if (theVersion == null) {
197                        ourLog.info("Creating new FhirContext with auto-detected version [{}]. It is recommended to explicitly select a version for future compatibility by invoking FhirContext.forDstuX()",
198                                myVersion.getVersion().name());
199                } else {
200                        if ("true".equals(System.getProperty("unit_test_mode"))) {
201                                String calledAt = ExceptionUtils.getStackFrames(new Throwable())[4];
202                                ourLog.info("Creating new FHIR context for FHIR version [{}]{}", myVersion.getVersion().name(), calledAt);
203                        } else {
204                                ourLog.info("Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name());
205                        }
206                }
207
208                myResourceTypesToScan = theResourceTypes;
209
210                /*
211                 * Check if we're running in Android mode and configure the context appropriately if so
212                 */
213                try {
214                        Class<?> clazz = Class.forName("ca.uhn.fhir.android.AndroidMarker");
215                        ourLog.info("Android mode detected, configuring FhirContext for Android operation");
216                        try {
217                                Method method = clazz.getMethod("configureContext", FhirContext.class);
218                                method.invoke(null, this);
219                        } catch (Throwable e) {
220                                ourLog.warn("Failed to configure context for Android operation", e);
221                        }
222                } catch (ClassNotFoundException e) {
223                        ourLog.trace("Android mode not detected");
224                }
225
226        }
227
228        /**
229         * @since 5.5.0
230         */
231        public static FhirContext forDstu3Cached() {
232                return forCached(FhirVersionEnum.DSTU3);
233        }
234
235        /**
236         * @since 5.5.0
237         */
238        public static FhirContext forR4Cached() {
239                return forCached(FhirVersionEnum.R4);
240        }
241
242        /**
243         * @since 5.5.0
244         */
245        public static FhirContext forR5Cached() {
246                return forCached(FhirVersionEnum.R5);
247        }
248
249        private String createUnknownResourceNameError(final String theResourceName, final FhirVersionEnum theVersion) {
250                return getLocalizer().getMessage(FhirContext.class, "unknownResourceName", theResourceName, theVersion);
251        }
252
253        private void ensureCustomTypeList() {
254                myClassToElementDefinition.clear();
255                if (myCustomTypes == null) {
256                        myCustomTypes = new ArrayList<>();
257                }
258        }
259
260        /**
261         * When encoding resources, this setting configures the parser to include
262         * an entry in the resource's metadata section which indicates which profile(s) the
263         * resource claims to conform to. The default is {@link AddProfileTagEnum#ONLY_FOR_CUSTOM}.
264         *
265         * @see #setAddProfileTagWhenEncoding(AddProfileTagEnum) for more information
266         */
267        public AddProfileTagEnum getAddProfileTagWhenEncoding() {
268                return myAddProfileTagWhenEncoding;
269        }
270
271        /**
272         * When encoding resources, this setting configures the parser to include
273         * an entry in the resource's metadata section which indicates which profile(s) the
274         * resource claims to conform to. The default is {@link AddProfileTagEnum#ONLY_FOR_CUSTOM}.
275         * <p>
276         * This feature is intended for situations where custom resource types are being used,
277         * avoiding the need to manually add profile declarations for these custom types.
278         * </p>
279         * <p>
280         * See <a href="http://jamesagnew.gihhub.io/hapi-fhir/doc_extensions.html">Profiling and Extensions</a>
281         * for more information on using custom types.
282         * </p>
283         * <p>
284         * Note that this feature automatically adds the profile, but leaves any profile tags
285         * which have been manually added in place as well.
286         * </p>
287         *
288         * @param theAddProfileTagWhenEncoding The add profile mode (must not be <code>null</code>)
289         */
290        public void setAddProfileTagWhenEncoding(final AddProfileTagEnum theAddProfileTagWhenEncoding) {
291                Validate.notNull(theAddProfileTagWhenEncoding, "theAddProfileTagWhenEncoding must not be null");
292                myAddProfileTagWhenEncoding = theAddProfileTagWhenEncoding;
293        }
294
295        Collection<RuntimeResourceDefinition> getAllResourceDefinitions() {
296                validateInitialized();
297                return myNameToResourceDefinition.values();
298        }
299
300        /**
301         * Returns the default resource type for the given profile
302         *
303         * @see #setDefaultTypeForProfile(String, Class)
304         */
305        public Class<? extends IBaseResource> getDefaultTypeForProfile(final String theProfile) {
306                validateInitialized();
307                return myDefaultTypeForProfile.get(theProfile);
308        }
309
310        /**
311         * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
312         * for extending the core library.
313         */
314        @SuppressWarnings("unchecked")
315        public BaseRuntimeElementDefinition<?> getElementDefinition(final Class<? extends IBase> theElementType) {
316                validateInitialized();
317                BaseRuntimeElementDefinition<?> retVal = myClassToElementDefinition.get(theElementType);
318                if (retVal == null) {
319                        retVal = scanDatatype((Class<? extends IElement>) theElementType);
320                }
321                return retVal;
322        }
323
324        /**
325         * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
326         * for extending the core library.
327         * <p>
328         * Note that this method is case insensitive!
329         * </p>
330         */
331        @Nullable
332        public BaseRuntimeElementDefinition<?> getElementDefinition(final String theElementName) {
333                validateInitialized();
334                return myNameToElementDefinition.get(theElementName.toLowerCase());
335        }
336
337        /**
338         * Returns all element definitions (resources, datatypes, etc.)
339         */
340        public Collection<BaseRuntimeElementDefinition<?>> getElementDefinitions() {
341                validateInitialized();
342                return Collections.unmodifiableCollection(myClassToElementDefinition.values());
343        }
344
345        /**
346         * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with
347         * caution
348         */
349        public HapiLocalizer getLocalizer() {
350                if (myLocalizer == null) {
351                        myLocalizer = new HapiLocalizer();
352                }
353                return myLocalizer;
354        }
355
356        /**
357         * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with
358         * caution
359         */
360        public void setLocalizer(final HapiLocalizer theMessages) {
361                myLocalizer = theMessages;
362        }
363
364        public INarrativeGenerator getNarrativeGenerator() {
365                return myNarrativeGenerator;
366        }
367
368        public void setNarrativeGenerator(final INarrativeGenerator theNarrativeGenerator) {
369                myNarrativeGenerator = theNarrativeGenerator;
370        }
371
372        /**
373         * Returns the parser options object which will be used to supply default
374         * options to newly created parsers
375         *
376         * @return The parser options - Will not return <code>null</code>
377         */
378        public ParserOptions getParserOptions() {
379                return myParserOptions;
380        }
381
382        /**
383         * Sets the parser options object which will be used to supply default
384         * options to newly created parsers
385         *
386         * @param theParserOptions The parser options object - Must not be <code>null</code>
387         */
388        public void setParserOptions(final ParserOptions theParserOptions) {
389                Validate.notNull(theParserOptions, "theParserOptions must not be null");
390                myParserOptions = theParserOptions;
391        }
392
393        /**
394         * Get the configured performance options
395         */
396        public Set<PerformanceOptionsEnum> getPerformanceOptions() {
397                return myPerformanceOptions;
398        }
399
400        // /**
401        // * Return an unmodifiable collection containing all known resource definitions
402        // */
403        // public Collection<RuntimeResourceDefinition> getResourceDefinitions() {
404        //
405        // Set<Class<? extends IBase>> datatypes = Collections.emptySet();
406        // Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> existing = Collections.emptyMap();
407        // HashMap<String, Class<? extends IBaseResource>> types = new HashMap<String, Class<? extends IBaseResource>>();
408        // ModelScanner.scanVersionPropertyFile(datatypes, types, myVersion.getVersion(), existing);
409        // for (int next : types.)
410        //
411        // return Collections.unmodifiableCollection(myIdToResourceDefinition.values());
412        // }
413
414        /**
415         * Sets the configured performance options
416         *
417         * @see PerformanceOptionsEnum for a list of available options
418         */
419        public void setPerformanceOptions(final Collection<PerformanceOptionsEnum> theOptions) {
420                myPerformanceOptions.clear();
421                if (theOptions != null) {
422                        myPerformanceOptions.addAll(theOptions);
423                }
424        }
425
426        /**
427         * Sets the configured performance options
428         *
429         * @see PerformanceOptionsEnum for a list of available options
430         */
431        public void setPerformanceOptions(final PerformanceOptionsEnum... thePerformanceOptions) {
432                Collection<PerformanceOptionsEnum> asList = null;
433                if (thePerformanceOptions != null) {
434                        asList = Arrays.asList(thePerformanceOptions);
435                }
436                setPerformanceOptions(asList);
437        }
438
439        /**
440         * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
441         * for extending the core library.
442         */
443        public RuntimeResourceDefinition getResourceDefinition(final Class<? extends IBaseResource> theResourceType) {
444                validateInitialized();
445                Validate.notNull(theResourceType, "theResourceType can not be null");
446
447                if (Modifier.isAbstract(theResourceType.getModifiers())) {
448                        throw new IllegalArgumentException("Can not scan abstract or interface class (resource definitions must be concrete classes): " + theResourceType.getName());
449                }
450
451                RuntimeResourceDefinition retVal = (RuntimeResourceDefinition) myClassToElementDefinition.get(theResourceType);
452                if (retVal == null) {
453                        retVal = scanResourceType(theResourceType);
454                }
455                return retVal;
456        }
457
458        public RuntimeResourceDefinition getResourceDefinition(final FhirVersionEnum theVersion, final String theResourceName) {
459                Validate.notNull(theVersion, "theVersion can not be null");
460                validateInitialized();
461
462                if (theVersion.equals(myVersion.getVersion())) {
463                        return getResourceDefinition(theResourceName);
464                }
465
466                Map<String, Class<? extends IBaseResource>> nameToType = myVersionToNameToResourceType.get(theVersion);
467                if (nameToType == null) {
468                        nameToType = new HashMap<>();
469                        Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> existing = new HashMap<>();
470                        ModelScanner.scanVersionPropertyFile(null, nameToType, theVersion, existing);
471
472                        Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> newVersionToNameToResourceType = new HashMap<>();
473                        newVersionToNameToResourceType.putAll(myVersionToNameToResourceType);
474                        newVersionToNameToResourceType.put(theVersion, nameToType);
475                        myVersionToNameToResourceType = newVersionToNameToResourceType;
476                }
477
478                Class<? extends IBaseResource> resourceType = nameToType.get(theResourceName.toLowerCase());
479                if (resourceType == null) {
480                        throw new DataFormatException(createUnknownResourceNameError(theResourceName, theVersion));
481                }
482
483                return getResourceDefinition(resourceType);
484        }
485
486        /**
487         * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
488         * for extending the core library.
489         */
490        public RuntimeResourceDefinition getResourceDefinition(final IBaseResource theResource) {
491                validateInitialized();
492                Validate.notNull(theResource, "theResource must not be null");
493                return getResourceDefinition(theResource.getClass());
494        }
495
496        /**
497         * Returns the name of a given resource class.
498         */
499        public String getResourceType(final Class<? extends IBaseResource> theResourceType) {
500                return getResourceDefinition(theResourceType).getName();
501        }
502
503        /**
504         * Returns the name of the scanned runtime model for the given type. This is an advanced feature which is generally only needed
505         * for extending the core library.
506         */
507        public String getResourceType(final IBaseResource theResource) {
508                return getResourceDefinition(theResource).getName();
509        }
510
511        /*
512         * Returns the type of the scanned runtime model for the given type. This is an advanced feature which is generally only needed
513         * for extending the core library.
514         * <p>
515         * Note that this method is case insensitive!
516         * </p>
517         *
518         * @throws DataFormatException If the resource name is not known
519         */
520        public String getResourceType(final String theResourceName) throws DataFormatException {
521                return getResourceDefinition(theResourceName).getName();
522        }
523
524        /*
525         * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
526         * for extending the core library.
527         * <p>
528         * Note that this method is case insensitive!
529         * </p>
530         *
531         * @throws DataFormatException If the resource name is not known
532         */
533        public RuntimeResourceDefinition getResourceDefinition(final String theResourceName) throws DataFormatException {
534                validateInitialized();
535                Validate.notBlank(theResourceName, "theResourceName must not be blank");
536
537                String resourceName = theResourceName.toLowerCase();
538                RuntimeResourceDefinition retVal = myNameToResourceDefinition.get(resourceName);
539
540                if (retVal == null) {
541                        Class<? extends IBaseResource> clazz = myNameToResourceType.get(resourceName.toLowerCase());
542                        if (clazz == null) {
543                                // ***********************************************************************
544                                // Multiple spots in HAPI FHIR and Smile CDR depend on DataFormatException
545                                // being thrown by this method, don't change that.
546                                // ***********************************************************************
547                                throw new DataFormatException(createUnknownResourceNameError(theResourceName, myVersion.getVersion()));
548                        }
549                        if (IBaseResource.class.isAssignableFrom(clazz)) {
550                                retVal = scanResourceType(clazz);
551                        }
552                }
553                return retVal;
554        }
555
556        /**
557         * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
558         * for extending the core library.
559         */
560        public RuntimeResourceDefinition getResourceDefinitionById(final String theId) {
561                validateInitialized();
562                return myIdToResourceDefinition.get(theId);
563        }
564
565        /**
566         * Returns the scanned runtime models. This is an advanced feature which is generally only needed for extending the
567         * core library.
568         */
569        public Collection<RuntimeResourceDefinition> getResourceDefinitionsWithExplicitId() {
570                validateInitialized();
571                return myIdToResourceDefinition.values();
572        }
573
574        /**
575         * Returns an unmodifiable set containing all resource names known to this
576         * context
577         *
578         * @since 5.1.0
579         */
580        public Set<String> getResourceTypes() {
581                Set<String> resourceNames = myResourceNames;
582                if (resourceNames == null) {
583                        resourceNames = buildResourceNames();
584                        myResourceNames = resourceNames;
585                }
586                return resourceNames;
587        }
588
589        @Nonnull
590        private Set<String> buildResourceNames() {
591                Set<String> retVal = new HashSet<>();
592                Properties props = new Properties();
593                try (InputStream propFile = myVersion.getFhirVersionPropertiesFile()) {
594                        props.load(propFile);
595                } catch (IOException e) {
596                        throw new ConfigurationException("Failed to load version properties file", e);
597                }
598                Enumeration<?> propNames = props.propertyNames();
599                while (propNames.hasMoreElements()) {
600                        String next = (String) propNames.nextElement();
601                        if (next.startsWith("resource.")) {
602                                retVal.add(next.substring("resource.".length()).trim());
603                        }
604                }
605                return retVal;
606        }
607
608        /**
609         * Get the restful client factory. If no factory has been set, this will be initialized with
610         * a new ApacheRestfulClientFactory.
611         *
612         * @return the factory used to create the restful clients
613         */
614        public IRestfulClientFactory getRestfulClientFactory() {
615                if (myRestfulClientFactory == null) {
616                        try {
617                                myRestfulClientFactory = (IRestfulClientFactory) ReflectionUtil.newInstance(Class.forName("ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory"), FhirContext.class, this);
618                        } catch (ClassNotFoundException e) {
619                                throw new ConfigurationException("hapi-fhir-client does not appear to be on the classpath");
620                        }
621                }
622                return myRestfulClientFactory;
623        }
624
625        /**
626         * Set the restful client factory
627         *
628         * @param theRestfulClientFactory The new client factory (must not be null)
629         */
630        public void setRestfulClientFactory(final IRestfulClientFactory theRestfulClientFactory) {
631                Validate.notNull(theRestfulClientFactory, "theRestfulClientFactory must not be null");
632                this.myRestfulClientFactory = theRestfulClientFactory;
633        }
634
635        public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
636                validateInitialized();
637                return myRuntimeChildUndeclaredExtensionDefinition;
638        }
639
640        /**
641         * Returns the validation support module configured for this context, creating a default
642         * implementation if no module has been passed in via the {@link #setValidationSupport(IValidationSupport)}
643         * method
644         *
645         * @see #setValidationSupport(IValidationSupport)
646         */
647        public IValidationSupport getValidationSupport() {
648                IValidationSupport retVal = myValidationSupport;
649                if (retVal == null) {
650                        retVal = new DefaultProfileValidationSupport(this);
651
652                        /*
653                         * If hapi-fhir-validation is on the classpath, we can create a much more robust
654                         * validation chain using the classes found in that package
655                         */
656                        String inMemoryTermSvcType = "org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport";
657                        String commonCodeSystemsSupportType = "org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService";
658                        if (ReflectionUtil.typeExists(inMemoryTermSvcType)) {
659                                IValidationSupport inMemoryTermSvc = ReflectionUtil.newInstanceOrReturnNull(inMemoryTermSvcType, IValidationSupport.class, new Class<?>[]{FhirContext.class}, new Object[]{this});
660                                IValidationSupport commonCodeSystemsSupport = ReflectionUtil.newInstanceOrReturnNull(commonCodeSystemsSupportType, IValidationSupport.class, new Class<?>[]{FhirContext.class}, new Object[]{this});
661                                retVal = ReflectionUtil.newInstanceOrReturnNull("org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain", IValidationSupport.class, new Class<?>[]{IValidationSupport[].class}, new Object[]{new IValidationSupport[]{
662                                        retVal,
663                                        inMemoryTermSvc,
664                                        commonCodeSystemsSupport
665                                }});
666                                assert retVal != null : "Failed to instantiate " + "org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain";
667                        }
668
669
670                        myValidationSupport = retVal;
671                }
672                return retVal;
673        }
674
675        /**
676         * Sets the validation support module to use for this context. The validation support module
677         * is used to supply underlying infrastructure such as conformance resources (StructureDefinition, ValueSet, etc)
678         * as well as to provide terminology services to modules such as the validator and FluentPath executor
679         */
680        public void setValidationSupport(IValidationSupport theValidationSupport) {
681                myValidationSupport = theValidationSupport;
682        }
683
684        public IFhirVersion getVersion() {
685                return myVersion;
686        }
687
688        /**
689         * Returns <code>true</code> if any default types for specific profiles have been defined
690         * within this context.
691         *
692         * @see #setDefaultTypeForProfile(String, Class)
693         * @see #getDefaultTypeForProfile(String)
694         */
695        public boolean hasDefaultTypeForProfile() {
696                validateInitialized();
697                return !myDefaultTypeForProfile.isEmpty();
698        }
699
700        /**
701         * @return Returns <code>true</code> if the XML serialization format is supported, based on the
702         * available libraries on the classpath.
703         *
704         * @since 5.4.0
705         */
706        public boolean isFormatXmlSupported() {
707                Boolean retVal = myFormatXmlSupported;
708                if (retVal == null) {
709                        retVal = tryToInitParser(() -> newXmlParser());
710                        myFormatXmlSupported = retVal;
711                }
712                return retVal;
713        }
714
715        /**
716         * @return Returns <code>true</code> if the JSON serialization format is supported, based on the
717         * available libraries on the classpath.
718         *
719         * @since 5.4.0
720         */
721        public boolean isFormatJsonSupported() {
722                Boolean retVal = myFormatJsonSupported;
723                if (retVal == null) {
724                        retVal = tryToInitParser(() -> newJsonParser());
725                        myFormatJsonSupported = retVal;
726                }
727                return retVal;
728        }
729
730        /**
731         * @return Returns <code>true</code> if the RDF serialization format is supported, based on the
732         * available libraries on the classpath.
733         *
734         * @since 5.4.0
735         */
736        public boolean isFormatRdfSupported() {
737                Boolean retVal = myFormatRdfSupported;
738                if (retVal == null) {
739                        retVal = tryToInitParser(() -> newRDFParser());
740                        myFormatRdfSupported = retVal;
741                }
742                return retVal;
743        }
744
745        public IVersionSpecificBundleFactory newBundleFactory() {
746                return myVersion.newBundleFactory(this);
747        }
748
749        /**
750         * @since 2.2
751         * @deprecated Deprecated in HAPI FHIR 5.0.0. Use {@link #newFhirPath()} instead.
752         */
753        @Deprecated
754        public IFhirPath newFluentPath() {
755                return newFhirPath();
756        }
757
758        /**
759         * Creates a new FhirPath engine which can be used to evaluate
760         * path expressions over FHIR resources. Note that this engine will use the
761         * {@link IValidationSupport context validation support} module which is
762         * configured on the context at the time this method is called.
763         * <p>
764         * In other words, you may wish to call {@link #setValidationSupport(IValidationSupport)} before
765         * calling {@link #newFluentPath()}
766         * </p>
767         * <p>
768         * Note that this feature was added for FHIR DSTU3 and is not available
769         * for contexts configured to use an older version of FHIR. Calling this method
770         * on a context for a previous version of fhir will result in an
771         * {@link UnsupportedOperationException}
772         * </p>
773         *
774         * @since 5.0.0
775         */
776        public IFhirPath newFhirPath() {
777                return myVersion.createFhirPathExecutor(this);
778        }
779
780        /**
781         * Create and return a new JSON parser.
782         *
783         * <p>
784         * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread
785         * or every message being parsed/encoded.
786         * </p>
787         * <p>
788         * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed
789         * without incurring any performance penalty
790         * </p>
791         */
792        public IParser newJsonParser() {
793                return new JsonParser(this, myParserErrorHandler);
794        }
795
796        /**
797         * Create and return a new RDF parser.
798         *
799         * <p>
800         * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread
801         * or every message being parsed/encoded.
802         * </p>
803         * <p>
804         * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed
805         * without incurring any performance penalty
806         * </p>
807         */
808        public IParser newRDFParser() {
809                return new RDFParser(this, myParserErrorHandler, Lang.TURTLE);
810        }
811
812        /**
813         * Instantiates a new client instance. This method requires an interface which is defined specifically for your use
814         * cases to contain methods for each of the RESTful operations you wish to implement (e.g. "read ImagingStudy",
815         * "search Patient by identifier", etc.). This interface must extend {@link IRestfulClient} (or commonly its
816         * sub-interface {@link IBasicClient}). See the <a
817         * href="https://hapifhir.io/hapi-fhir/docs/client/introduction.html">RESTful Client</a> documentation for more
818         * information on how to define this interface.
819         *
820         * <p>
821         * Performance Note: <b>This method is cheap</b> to call, and may be called once for every operation invocation
822         * without incurring any performance penalty
823         * </p>
824         *
825         * @param theClientType The client type, which is an interface type to be instantiated
826         * @param theServerBase The URL of the base for the restful FHIR server to connect to
827         * @return A newly created client
828         * @throws ConfigurationException If the interface type is not an interface
829         */
830        public <T extends IRestfulClient> T newRestfulClient(final Class<T> theClientType, final String theServerBase) {
831                return getRestfulClientFactory().newClient(theClientType, theServerBase);
832        }
833
834        /**
835         * Instantiates a new generic client. A generic client is able to perform any of the FHIR RESTful operations against
836         * a compliant server, but does not have methods defining the specific functionality required (as is the case with
837         * {@link #newRestfulClient(Class, String) non-generic clients}).
838         *
839         * <p>
840         * Performance Note: <b>This method is cheap</b> to call, and may be called once for every operation invocation
841         * without incurring any performance penalty
842         * </p>
843         *
844         * @param theServerBase The URL of the base for the restful FHIR server to connect to
845         */
846        public IGenericClient newRestfulGenericClient(final String theServerBase) {
847                return getRestfulClientFactory().newGenericClient(theServerBase);
848        }
849
850        public FhirTerser newTerser() {
851                return new FhirTerser(this);
852        }
853
854        /**
855         * Create a new validator instance.
856         * <p>
857         * Note on thread safety: Validators are thread safe, you may use a single validator
858         * in multiple threads. (This is in contrast to parsers)
859         * </p>
860         */
861        public FhirValidator newValidator() {
862                return new FhirValidator(this);
863        }
864
865        public ViewGenerator newViewGenerator() {
866                return new ViewGenerator(this);
867        }
868
869        /**
870         * Create and return a new XML parser.
871         *
872         * <p>
873         * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread
874         * or every message being parsed/encoded.
875         * </p>
876         * <p>
877         * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed
878         * without incurring any performance penalty
879         * </p>
880         */
881        public IParser newXmlParser() {
882                return new XmlParser(this, myParserErrorHandler);
883        }
884
885        /**
886         * This method may be used to register a custom resource or datatype. Note that by using
887         * custom types, you are creating a system that will not interoperate with other systems that
888         * do not know about your custom type. There are valid reasons however for wanting to create
889         * custom types and this method can be used to enable them.
890         * <p>
891         * <b>THREAD SAFETY WARNING:</b> This method is not thread safe. It should be called before any
892         * threads are able to call any methods on this context.
893         * </p>
894         *
895         * @param theType The custom type to add (must not be <code>null</code>)
896         */
897        public void registerCustomType(final Class<? extends IBase> theType) {
898                Validate.notNull(theType, "theType must not be null");
899
900                ensureCustomTypeList();
901                myCustomTypes.add(theType);
902        }
903
904        /**
905         * This method may be used to register a custom resource or datatype. Note that by using
906         * custom types, you are creating a system that will not interoperate with other systems that
907         * do not know about your custom type. There are valid reasons however for wanting to create
908         * custom types and this method can be used to enable them.
909         * <p>
910         * <b>THREAD SAFETY WARNING:</b> This method is not thread safe. It should be called before any
911         * threads are able to call any methods on this context.
912         * </p>
913         *
914         * @param theTypes The custom types to add (must not be <code>null</code> or contain null elements in the collection)
915         */
916        public void registerCustomTypes(final Collection<Class<? extends IBase>> theTypes) {
917                Validate.notNull(theTypes, "theTypes must not be null");
918                Validate.noNullElements(theTypes.toArray(), "theTypes must not contain any null elements");
919
920                ensureCustomTypeList();
921
922                myCustomTypes.addAll(theTypes);
923        }
924
925        private BaseRuntimeElementDefinition<?> scanDatatype(final Class<? extends IElement> theResourceType) {
926                ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<>();
927                resourceTypes.add(theResourceType);
928                Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> defs = scanResourceTypes(resourceTypes);
929                return defs.get(theResourceType);
930        }
931
932        private RuntimeResourceDefinition scanResourceType(final Class<? extends IBaseResource> theResourceType) {
933                ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<>();
934                resourceTypes.add(theResourceType);
935                Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> defs = scanResourceTypes(resourceTypes);
936                return (RuntimeResourceDefinition) defs.get(theResourceType);
937        }
938
939        private synchronized Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> scanResourceTypes(final Collection<Class<? extends IElement>> theResourceTypes) {
940                List<Class<? extends IBase>> typesToScan = new ArrayList<>();
941                if (theResourceTypes != null) {
942                        typesToScan.addAll(theResourceTypes);
943                }
944                if (myCustomTypes != null) {
945                        typesToScan.addAll(myCustomTypes);
946                        myCustomTypes = null;
947                }
948
949                ModelScanner scanner = new ModelScanner(this, myVersion.getVersion(), myClassToElementDefinition, typesToScan);
950                if (myRuntimeChildUndeclaredExtensionDefinition == null) {
951                        myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition();
952                }
953
954                Map<String, BaseRuntimeElementDefinition<?>> nameToElementDefinition = new HashMap<>();
955                nameToElementDefinition.putAll(myNameToElementDefinition);
956                for (Entry<String, BaseRuntimeElementDefinition<?>> next : scanner.getNameToElementDefinitions().entrySet()) {
957                        if (!nameToElementDefinition.containsKey(next.getKey())) {
958                                nameToElementDefinition.put(next.getKey().toLowerCase(), next.getValue());
959                        }
960                }
961
962                Map<String, RuntimeResourceDefinition> nameToResourceDefinition = new HashMap<>();
963                nameToResourceDefinition.putAll(myNameToResourceDefinition);
964                for (Entry<String, RuntimeResourceDefinition> next : scanner.getNameToResourceDefinition().entrySet()) {
965                        if (!nameToResourceDefinition.containsKey(next.getKey())) {
966                                nameToResourceDefinition.put(next.getKey(), next.getValue());
967                        }
968                }
969
970                Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> classToElementDefinition = new HashMap<>();
971                classToElementDefinition.putAll(myClassToElementDefinition);
972                classToElementDefinition.putAll(scanner.getClassToElementDefinitions());
973                for (BaseRuntimeElementDefinition<?> next : classToElementDefinition.values()) {
974                        if (next instanceof RuntimeResourceDefinition) {
975                                if ("Bundle".equals(next.getName())) {
976                                        if (!IBaseBundle.class.isAssignableFrom(next.getImplementingClass())) {
977                                                throw new ConfigurationException("Resource type declares resource name Bundle but does not implement IBaseBundle");
978                                        }
979                                }
980                        }
981                }
982
983                Map<String, RuntimeResourceDefinition> idToElementDefinition = new HashMap<>();
984                idToElementDefinition.putAll(myIdToResourceDefinition);
985                idToElementDefinition.putAll(scanner.getIdToResourceDefinition());
986
987                myNameToElementDefinition = nameToElementDefinition;
988                myClassToElementDefinition = classToElementDefinition;
989                myIdToResourceDefinition = idToElementDefinition;
990                myNameToResourceDefinition = nameToResourceDefinition;
991
992                myNameToResourceType = scanner.getNameToResourceType();
993
994                myInitialized = true;
995                return classToElementDefinition;
996        }
997
998        /**
999         * Sets the default type which will be used when parsing a resource that is found to be
1000         * of the given profile.
1001         * <p>
1002         * For example, this method is invoked with the profile string of
1003         * <code>"http://example.com/some_patient_profile"</code> and the type of <code>MyPatient.class</code>,
1004         * if the parser is parsing a resource and finds that it declares that it conforms to that profile,
1005         * the <code>MyPatient</code> type will be used unless otherwise specified.
1006         * </p>
1007         *
1008         * @param theProfile The profile string, e.g. <code>"http://example.com/some_patient_profile"</code>. Must not be
1009         *                   <code>null</code> or empty.
1010         * @param theClass   The resource type, or <code>null</code> to clear any existing type
1011         */
1012        public void setDefaultTypeForProfile(final String theProfile, final Class<? extends IBaseResource> theClass) {
1013                Validate.notBlank(theProfile, "theProfile must not be null or empty");
1014                if (theClass == null) {
1015                        myDefaultTypeForProfile.remove(theProfile);
1016                } else {
1017                        myDefaultTypeForProfile.put(theProfile, theClass);
1018                }
1019        }
1020
1021        /**
1022         * Sets a parser error handler to use by default on all parsers
1023         *
1024         * @param theParserErrorHandler The error handler
1025         */
1026        public void setParserErrorHandler(final IParserErrorHandler theParserErrorHandler) {
1027                Validate.notNull(theParserErrorHandler, "theParserErrorHandler must not be null");
1028                myParserErrorHandler = theParserErrorHandler;
1029        }
1030
1031        @SuppressWarnings({"cast"})
1032        private List<Class<? extends IElement>> toElementList(final Collection<Class<? extends IBaseResource>> theResourceTypes) {
1033                if (theResourceTypes == null) {
1034                        return null;
1035                }
1036                List<Class<? extends IElement>> resTypes = new ArrayList<>();
1037                for (Class<? extends IBaseResource> next : theResourceTypes) {
1038                        resTypes.add(next);
1039                }
1040                return resTypes;
1041        }
1042
1043        private void validateInitialized() {
1044                // See #610
1045                if (!myInitialized) {
1046                        synchronized (this) {
1047                                if (!myInitialized && !myInitializing) {
1048                                        myInitializing = true;
1049                                        scanResourceTypes(toElementList(myResourceTypesToScan));
1050                                }
1051                        }
1052                }
1053        }
1054
1055        @Override
1056        public String toString() {
1057                return "FhirContext[" + myVersion.getVersion().name() + "]";
1058        }
1059
1060        // TODO KHS add the other primitive types
1061        public IPrimitiveType<Boolean> getPrimitiveBoolean(Boolean theValue) {
1062                IPrimitiveType<Boolean> retval = (IPrimitiveType<Boolean>) getElementDefinition("boolean").newInstance();
1063                retval.setValue(theValue);
1064                return retval;
1065        }
1066
1067        private static boolean tryToInitParser(Runnable run) {
1068                boolean retVal;
1069                try {
1070                        run.run();
1071                        retVal = true;
1072                } catch (Exception | NoClassDefFoundError e) {
1073                        retVal = false;
1074                }
1075                return retVal;
1076        }
1077
1078        /**
1079         * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU2}
1080         */
1081        public static FhirContext forDstu2() {
1082                return new FhirContext(FhirVersionEnum.DSTU2);
1083        }
1084
1085        /**
1086         * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2_HL7ORG DSTU2} (using the Reference
1087         * Implementation Structures)
1088         */
1089        public static FhirContext forDstu2Hl7Org() {
1090                return new FhirContext(FhirVersionEnum.DSTU2_HL7ORG);
1091        }
1092
1093        /**
1094         * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU2} (2016 May DSTU3 Snapshot)
1095         */
1096        public static FhirContext forDstu2_1() {
1097                return new FhirContext(FhirVersionEnum.DSTU2_1);
1098        }
1099
1100        /**
1101         * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU3 DSTU3}
1102         *
1103         * @since 1.4
1104         */
1105        public static FhirContext forDstu3() {
1106                return new FhirContext(FhirVersionEnum.DSTU3);
1107        }
1108
1109        /**
1110         * Creates and returns a new FhirContext with version {@link FhirVersionEnum#R4 R4}
1111         *
1112         * @since 3.0.0
1113         */
1114        public static FhirContext forR4() {
1115                return new FhirContext(FhirVersionEnum.R4);
1116        }
1117
1118        /**
1119         * Creates and returns a new FhirContext with version {@link FhirVersionEnum#R5 R5}
1120         *
1121         * @since 4.0.0
1122         */
1123        public static FhirContext forR5() {
1124                return new FhirContext(FhirVersionEnum.R5);
1125        }
1126
1127        /**
1128         * Returns a statically cached {@literal FhirContext} instance for the given version, creating one if none exists in the
1129         * cache. One FhirContext will be kept in the cache for each FHIR version that is requested (by calling
1130         * this method for that version), and the cache will never be expired.
1131         *
1132         * @since 5.1.0
1133         */
1134        public static FhirContext forCached(FhirVersionEnum theFhirVersionEnum) {
1135                return ourStaticContexts.computeIfAbsent(theFhirVersionEnum, v -> new FhirContext(v));
1136        }
1137
1138        private static Collection<Class<? extends IBaseResource>> toCollection(Class<? extends IBaseResource> theResourceType) {
1139                ArrayList<Class<? extends IBaseResource>> retVal = new ArrayList<>(1);
1140                retVal.add(theResourceType);
1141                return retVal;
1142        }
1143
1144        @SuppressWarnings("unchecked")
1145        private static List<Class<? extends IBaseResource>> toCollection(final Class<?>[] theResourceTypes) {
1146                ArrayList<Class<? extends IBaseResource>> retVal = new ArrayList<Class<? extends IBaseResource>>(1);
1147                for (Class<?> clazz : theResourceTypes) {
1148                        if (!IResource.class.isAssignableFrom(clazz)) {
1149                                throw new IllegalArgumentException(clazz.getCanonicalName() + " is not an instance of " + IResource.class.getSimpleName());
1150                        }
1151                        retVal.add((Class<? extends IResource>) clazz);
1152                }
1153                return retVal;
1154        }
1155}