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}