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