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