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