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