001package ca.uhn.fhir.context;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2016 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import java.lang.reflect.Modifier;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031import org.apache.commons.lang3.Validate;
032import org.apache.commons.lang3.text.WordUtils;
033import org.hl7.fhir.instance.model.api.IBase;
034import org.hl7.fhir.instance.model.api.IBaseResource;
035
036import ca.uhn.fhir.i18n.HapiLocalizer;
037import ca.uhn.fhir.model.api.IElement;
038import ca.uhn.fhir.model.api.IFhirVersion;
039import ca.uhn.fhir.model.api.IResource;
040import ca.uhn.fhir.model.view.ViewGenerator;
041import ca.uhn.fhir.narrative.INarrativeGenerator;
042import ca.uhn.fhir.parser.DataFormatException;
043import ca.uhn.fhir.parser.IParser;
044import ca.uhn.fhir.parser.IParserErrorHandler;
045import ca.uhn.fhir.parser.JsonParser;
046import ca.uhn.fhir.parser.LenientErrorHandler;
047import ca.uhn.fhir.parser.XmlParser;
048import ca.uhn.fhir.rest.client.IGenericClient;
049import ca.uhn.fhir.rest.client.IRestfulClientFactory;
050import ca.uhn.fhir.rest.client.RestfulClientFactory;
051import ca.uhn.fhir.rest.client.api.IBasicClient;
052import ca.uhn.fhir.rest.client.api.IRestfulClient;
053import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory;
054import ca.uhn.fhir.util.FhirTerser;
055import ca.uhn.fhir.util.VersionUtil;
056import ca.uhn.fhir.validation.FhirValidator;
057
058/**
059 * The FHIR context is the central starting point for the use of the HAPI FHIR API. It should be created once, and then
060 * used as a factory for various other types of objects (parsers, clients, etc.).
061 * 
062 * <p>
063 * Important usage notes:
064 * </p>
065 * <ul>
066 * <li>Thread safety: <b>This class is thread safe</b> and may be shared between multiple processing threads.</li>
067 * <li>
068 * Performance: <b>This class is expensive</b> to create, as it scans every resource class it needs to parse or encode
069 * to build up an internal model of those classes. For that reason, you should try to create one FhirContext instance
070 * which remains for the life of your application and reuse that instance. Note that it will not cause problems to
071 * create multiple instances (ie. resources originating from one FhirContext may be passed to parsers originating from
072 * another) but you will incur a performance penalty if a new FhirContext is created for every message you parse/encode.
073 * </li>
074 * </ul>
075 */
076public class FhirContext {
077
078        private static final List<Class<? extends IBaseResource>> EMPTY_LIST = Collections.emptyList();
079        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContext.class);
080        private volatile Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition = Collections.emptyMap();
081        private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap();
082        private HapiLocalizer myLocalizer = new HapiLocalizer();
083        private volatile Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinition = Collections.emptyMap();
084        private volatile Map<String, RuntimeResourceDefinition> myNameToResourceDefinition = Collections.emptyMap();
085        private volatile Map<String, Class<? extends IBaseResource>> myNameToResourceType;
086        private volatile INarrativeGenerator myNarrativeGenerator;
087        private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler();
088        private volatile IRestfulClientFactory myRestfulClientFactory;
089        private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
090        private final IFhirVersion myVersion;
091        private Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> myVersionToNameToResourceType = Collections.emptyMap();
092
093        /**
094         * Default constructor. In most cases this is the right constructor to use.
095         */
096        public FhirContext() {
097                this(EMPTY_LIST);
098        }
099
100        public FhirContext(Class<? extends IBaseResource> theResourceType) {
101                this(toCollection(theResourceType));
102        }
103
104        public FhirContext(Class<?>... theResourceTypes) {
105                this(toCollection(theResourceTypes));
106        }
107
108        public FhirContext(Collection<Class<? extends IBaseResource>> theResourceTypes) {
109                this(null, theResourceTypes);
110        }
111
112        public FhirContext(FhirVersionEnum theVersion) {
113                this(theVersion, null);
114        }
115
116        private FhirContext(FhirVersionEnum theVersion, Collection<Class<? extends IBaseResource>> theResourceTypes) {
117                VersionUtil.getVersion();
118                
119                if (theVersion != null) {
120                        if (!theVersion.isPresentOnClasspath()) {
121                                throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructuresForSpecifiedVersion", theVersion.name()));
122                        }
123                        myVersion = theVersion.getVersionImplementation();
124                } else if (FhirVersionEnum.DSTU1.isPresentOnClasspath()) {
125                        myVersion = FhirVersionEnum.DSTU1.getVersionImplementation();
126                } else if (FhirVersionEnum.DSTU2.isPresentOnClasspath()) {
127                        myVersion = FhirVersionEnum.DSTU2.getVersionImplementation();
128                } else if (FhirVersionEnum.DSTU2_HL7ORG.isPresentOnClasspath()) {
129                        myVersion = FhirVersionEnum.DSTU2_HL7ORG.getVersionImplementation();
130                } else {
131                        throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructures"));
132                }
133
134                if (theVersion == null) {
135                        ourLog.info("Creating new FhirContext with auto-detected version [{}]. It is recommended to explicitly select a version for future compatibility by invoking FhirContext.forDstuX()", myVersion.getVersion().name());
136                } else {
137                        ourLog.info("Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name());
138                }
139
140                scanResourceTypes(toElementList(theResourceTypes));
141        }
142
143        private String createUnknownResourceNameError(String theResourceName, FhirVersionEnum theVersion) {
144                return getLocalizer().getMessage(FhirContext.class, "unknownResourceName", theResourceName, theVersion);
145        }
146
147        /**
148         * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
149         * for extending the core library.
150         */
151        @SuppressWarnings("unchecked")
152        public BaseRuntimeElementDefinition<?> getElementDefinition(Class<? extends IBase> theElementType) {
153                BaseRuntimeElementDefinition<?> retVal = myClassToElementDefinition.get(theElementType);
154                if (retVal == null) {
155                        retVal = scanDatatype((Class<? extends IElement>) theElementType);
156                }
157                return retVal;
158        }
159
160        /**
161         * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
162         * for extending the core library.
163         */
164        public BaseRuntimeElementDefinition<?> getElementDefinition(String theElementName) {
165                return myNameToElementDefinition.get(theElementName);
166        }
167        
168        /** For unit tests only */
169        int getElementDefinitionCount() {
170                return myClassToElementDefinition.size();
171        }
172
173        /**
174         * Returns all element definitions (resources, datatypes, etc.)
175         */
176        public Collection<BaseRuntimeElementDefinition<?>> getElementDefinitions() {
177                return Collections.unmodifiableCollection(myClassToElementDefinition.values());
178        }
179
180        /**
181         * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with
182         * caution
183         */
184        public HapiLocalizer getLocalizer() {
185                if (myLocalizer == null) {
186                        myLocalizer = new HapiLocalizer();
187                }
188                return myLocalizer;
189        }
190
191        public INarrativeGenerator getNarrativeGenerator() {
192                return myNarrativeGenerator;
193        }
194
195        /**
196         * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
197         * for extending the core library.
198         */
199        @SuppressWarnings("unchecked")
200        public RuntimeResourceDefinition getResourceDefinition(Class<? extends IBaseResource> theResourceType) {
201                if (theResourceType == null) {
202                        throw new NullPointerException("theResourceType can not be null");
203                }
204                if (Modifier.isAbstract(theResourceType.getModifiers())) {
205                        throw new IllegalArgumentException("Can not scan abstract or interface class (resource definitions must be concrete classes): " + theResourceType.getName());
206                }
207
208                RuntimeResourceDefinition retVal = (RuntimeResourceDefinition) myClassToElementDefinition.get(theResourceType);
209                if (retVal == null) {
210                        retVal = scanResourceType((Class<? extends IResource>) theResourceType);
211                }
212                return retVal;
213        }
214
215        public RuntimeResourceDefinition getResourceDefinition(FhirVersionEnum theVersion, String theResourceName) {
216                Validate.notNull(theVersion, "theVersion can not be null");
217
218                if (theVersion.equals(myVersion.getVersion())) {
219                        return getResourceDefinition(theResourceName);
220                }
221
222                Map<String, Class<? extends IBaseResource>> nameToType = myVersionToNameToResourceType.get(theVersion);
223                if (nameToType == null) {
224                        nameToType = new HashMap<String, Class<? extends IBaseResource>>();
225                        ModelScanner.scanVersionPropertyFile(null, nameToType, theVersion);
226
227                        Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> newVersionToNameToResourceType = new HashMap<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>>();
228                        newVersionToNameToResourceType.putAll(myVersionToNameToResourceType);
229                        newVersionToNameToResourceType.put(theVersion, nameToType);
230                        myVersionToNameToResourceType = newVersionToNameToResourceType;
231                }
232
233                Class<? extends IBaseResource> resourceType = nameToType.get(theResourceName.toLowerCase());
234                if (resourceType == null) {
235                        throw new DataFormatException(createUnknownResourceNameError(theResourceName, theVersion));
236                }
237
238                return getResourceDefinition(resourceType);
239        }
240
241        /**
242         * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
243         * for extending the core library.
244         */
245        public RuntimeResourceDefinition getResourceDefinition(IBaseResource theResource) {
246                return getResourceDefinition(theResource.getClass());
247        }
248
249        /**
250         * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
251         * for extending the core library.
252         */
253        @SuppressWarnings("unchecked")
254        public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
255                Validate.notBlank(theResourceName, "theResourceName must not be blank");
256                
257                String resourceName = theResourceName;
258
259                /*
260                 * TODO: this is a bit of a hack, really we should have a translation table based on a property file or
261                 * something so that we can detect names like diagnosticreport
262                 */
263                if (Character.isLowerCase(resourceName.charAt(0))) {
264                        resourceName = WordUtils.capitalize(resourceName);
265                }
266
267                Validate.notBlank(resourceName, "Resource name must not be blank");
268
269                RuntimeResourceDefinition retVal = myNameToResourceDefinition.get(resourceName);
270
271                if (retVal == null) {
272                        Class<? extends IBaseResource> clazz = myNameToResourceType.get(resourceName.toLowerCase());
273                        if (clazz == null) {
274                                throw new DataFormatException(createUnknownResourceNameError(resourceName, myVersion.getVersion()));
275                        }
276                        if (IBaseResource.class.isAssignableFrom(clazz)) {
277                                retVal = scanResourceType((Class<? extends IResource>) clazz);
278                        }
279                }
280
281                return retVal;
282        }
283
284        /**
285         * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
286         * for extending the core library.
287         */
288        public RuntimeResourceDefinition getResourceDefinitionById(String theId) {
289                return myIdToResourceDefinition.get(theId);
290        }
291
292        /**
293         * Returns the scanned runtime models. This is an advanced feature which is generally only needed for extending the
294         * core library.
295         */
296        public Collection<RuntimeResourceDefinition> getResourceDefinitions() {
297                return myIdToResourceDefinition.values();
298        }
299
300        public IRestfulClientFactory getRestfulClientFactory() {
301                if (myRestfulClientFactory == null) {
302                        myRestfulClientFactory = new RestfulClientFactory(this);
303                }
304                return myRestfulClientFactory;
305        }
306
307        public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
308                return myRuntimeChildUndeclaredExtensionDefinition;
309        }
310
311        public IFhirVersion getVersion() {
312                return myVersion;
313        }
314
315        /**
316         * This method should be considered experimental and will likely change in future releases
317         * of HAPI. Use with caution!
318         */
319        public IVersionSpecificBundleFactory newBundleFactory() {
320                return myVersion.newBundleFactory(this);
321        }
322
323        /**
324         * Create and return a new JSON parser.
325         * 
326         * <p>
327         * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread
328         * or every message being parsed/encoded.
329         * </p>
330         * <p>
331         * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed
332         * without incurring any performance penalty
333         * </p>
334         */
335        public IParser newJsonParser() {
336                return new JsonParser(this, myParserErrorHandler);
337        }
338
339        /**
340         * Instantiates a new client instance. This method requires an interface which is defined specifically for your use
341         * cases to contain methods for each of the RESTful operations you wish to implement (e.g. "read ImagingStudy",
342         * "search Patient by identifier", etc.). This interface must extend {@link IRestfulClient} (or commonly its
343         * sub-interface {@link IBasicClient}). See the <a
344         * href="http://jamesagnew.github.io/hapi-fhir/doc_rest_client.html">RESTful Client</a> documentation for more
345         * information on how to define this interface.
346         * 
347         * <p>
348         * Performance Note: <b>This method is cheap</b> to call, and may be called once for every operation invocation
349         * without incurring any performance penalty
350         * </p>
351         * 
352         * @param theClientType
353         *            The client type, which is an interface type to be instantiated
354         * @param theServerBase
355         *            The URL of the base for the restful FHIR server to connect to
356         * @return A newly created client
357         * @throws ConfigurationException
358         *             If the interface type is not an interface
359         */
360        public <T extends IRestfulClient> T newRestfulClient(Class<T> theClientType, String theServerBase) {
361                return getRestfulClientFactory().newClient(theClientType, theServerBase);
362        }
363
364        /**
365         * Instantiates a new generic client. A generic client is able to perform any of the FHIR RESTful operations against
366         * a compliant server, but does not have methods defining the specific functionality required (as is the case with
367         * {@link #newRestfulClient(Class, String) non-generic clients}).
368         * 
369         * <p>
370         * Performance Note: <b>This method is cheap</b> to call, and may be called once for every operation invocation
371         * without incurring any performance penalty
372         * </p>
373         * 
374         * @param theServerBase
375         *            The URL of the base for the restful FHIR server to connect to
376         */
377        public IGenericClient newRestfulGenericClient(String theServerBase) {
378                return getRestfulClientFactory().newGenericClient(theServerBase);
379        }
380
381        public FhirTerser newTerser() {
382                return new FhirTerser(this);
383        }
384
385        /**
386         * Create a new validator instance.
387         * <p>
388         * Note on thread safety: Validators are thread safe, you may use a single validator 
389         * in multiple threads. (This is in contrast to parsers)
390         * </p>
391         */
392        public FhirValidator newValidator() {
393                return new FhirValidator(this);
394        }
395
396        public ViewGenerator newViewGenerator() {
397                return new ViewGenerator(this);
398        }
399
400        /**
401         * Create and return a new XML parser.
402         * 
403         * <p>
404         * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread
405         * or every message being parsed/encoded.
406         * </p>
407         * <p>
408         * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed
409         * without incurring any performance penalty
410         * </p>
411         */
412        public IParser newXmlParser() {
413                return new XmlParser(this, myParserErrorHandler);
414        }
415
416        private BaseRuntimeElementDefinition<?> scanDatatype(Class<? extends IElement> theResourceType) {
417                ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<Class<? extends IElement>>();
418                resourceTypes.add(theResourceType);
419                Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> defs = scanResourceTypes(resourceTypes);
420                return defs.get(theResourceType);
421        }
422
423        private RuntimeResourceDefinition scanResourceType(Class<? extends IResource> theResourceType) {
424                ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<Class<? extends IElement>>();
425                resourceTypes.add(theResourceType);
426                Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> defs = scanResourceTypes(resourceTypes);
427                return (RuntimeResourceDefinition) defs.get(theResourceType);
428        }
429
430        private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> scanResourceTypes(Collection<Class<? extends IElement>> theResourceTypes) {
431                ModelScanner scanner = new ModelScanner(this, myVersion.getVersion(), myClassToElementDefinition, theResourceTypes);
432                if (myRuntimeChildUndeclaredExtensionDefinition == null) {
433                        myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition();
434                }
435
436                Map<String, BaseRuntimeElementDefinition<?>> nameToElementDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>();
437                nameToElementDefinition.putAll(myNameToElementDefinition);
438                nameToElementDefinition.putAll(scanner.getNameToElementDefinitions());
439
440                Map<String, RuntimeResourceDefinition> nameToResourceDefinition = new HashMap<String, RuntimeResourceDefinition>();
441                nameToResourceDefinition.putAll(myNameToResourceDefinition);
442                nameToResourceDefinition.putAll(scanner.getNameToResourceDefinition());
443                
444                Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> classToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
445                classToElementDefinition.putAll(myClassToElementDefinition);
446                classToElementDefinition.putAll(scanner.getClassToElementDefinitions());
447
448                Map<String, RuntimeResourceDefinition> idToElementDefinition = new HashMap<String, RuntimeResourceDefinition>();
449                idToElementDefinition.putAll(myIdToResourceDefinition);
450                idToElementDefinition.putAll(scanner.getIdToResourceDefinition());
451
452                myNameToElementDefinition = nameToElementDefinition;
453                myClassToElementDefinition = classToElementDefinition;
454                myIdToResourceDefinition = idToElementDefinition;
455                myNameToResourceDefinition = nameToResourceDefinition;
456
457                myNameToResourceType = scanner.getNameToResourceType();
458
459                return classToElementDefinition;
460        }
461
462        /**
463         * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with
464         * caution
465         */
466        public void setLocalizer(HapiLocalizer theMessages) {
467                myLocalizer = theMessages;
468        }
469
470        public void setNarrativeGenerator(INarrativeGenerator theNarrativeGenerator) {
471                myNarrativeGenerator = theNarrativeGenerator;
472        }
473
474        /**
475         * Sets a parser error handler to use by default on all parsers
476         * 
477         * @param theParserErrorHandler The error handler
478         */
479        public void setParserErrorHandler(IParserErrorHandler theParserErrorHandler) {
480                Validate.notNull(theParserErrorHandler, "theParserErrorHandler must not be null");
481                myParserErrorHandler = theParserErrorHandler;
482        }
483
484        @SuppressWarnings({ "cast" })
485        private List<Class<? extends IElement>> toElementList(Collection<Class<? extends IBaseResource>> theResourceTypes) {
486                if (theResourceTypes == null) {
487                        return null;
488                }
489                List<Class<? extends IElement>> resTypes = new ArrayList<Class<? extends IElement>>();
490                for (Class<? extends IBaseResource> next : theResourceTypes) {
491                        resTypes.add((Class<? extends IElement>) next);
492                }
493                return resTypes;
494        }
495
496        /**
497         * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU1}
498         */
499        public static FhirContext forDstu1() {
500                return new FhirContext(FhirVersionEnum.DSTU1);
501        }
502
503        /**
504         * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU 2}
505         */
506        public static FhirContext forDstu2() {
507                return new FhirContext(FhirVersionEnum.DSTU2);
508        }
509
510        /**
511         * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU3 DSTU 3}
512         * 
513         * @since 1.4
514         */
515        public static FhirContext forDstu3() {
516                return new FhirContext(FhirVersionEnum.DSTU3);
517        }
518
519        public static FhirContext forDstu2Hl7Org() {
520                return new FhirContext(FhirVersionEnum.DSTU2_HL7ORG);
521        }
522
523        private static Collection<Class<? extends IBaseResource>> toCollection(Class<? extends IBaseResource> theResourceType) {
524                ArrayList<Class<? extends IBaseResource>> retVal = new ArrayList<Class<? extends IBaseResource>>(1);
525                retVal.add(theResourceType);
526                return retVal;
527        }
528
529        @SuppressWarnings("unchecked")
530        private static List<Class<? extends IBaseResource>> toCollection(Class<?>[] theResourceTypes) {
531                ArrayList<Class<? extends IBaseResource>> retVal = new ArrayList<Class<? extends IBaseResource>>(1);
532                for (Class<?> clazz : theResourceTypes) {
533                        if (!IResource.class.isAssignableFrom(clazz)) {
534                                throw new IllegalArgumentException(clazz.getCanonicalName() + " is not an instance of " + IResource.class.getSimpleName());
535                        }
536                        retVal.add((Class<? extends IResource>) clazz);
537                }
538                return retVal;
539        }
540
541}