001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2023 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.context; 021 022import ca.uhn.fhir.context.api.AddProfileTagEnum; 023import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; 024import ca.uhn.fhir.context.support.IValidationSupport; 025import ca.uhn.fhir.fhirpath.IFhirPath; 026import ca.uhn.fhir.i18n.HapiLocalizer; 027import ca.uhn.fhir.i18n.Msg; 028import ca.uhn.fhir.model.api.IElement; 029import ca.uhn.fhir.model.api.IFhirVersion; 030import ca.uhn.fhir.model.api.IResource; 031import ca.uhn.fhir.model.view.ViewGenerator; 032import ca.uhn.fhir.narrative.INarrativeGenerator; 033import ca.uhn.fhir.parser.DataFormatException; 034import ca.uhn.fhir.parser.IParser; 035import ca.uhn.fhir.parser.IParserErrorHandler; 036import ca.uhn.fhir.parser.JsonParser; 037import ca.uhn.fhir.parser.LenientErrorHandler; 038import ca.uhn.fhir.parser.NDJsonParser; 039import ca.uhn.fhir.parser.RDFParser; 040import ca.uhn.fhir.parser.XmlParser; 041import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory; 042import ca.uhn.fhir.rest.client.api.IBasicClient; 043import ca.uhn.fhir.rest.client.api.IGenericClient; 044import ca.uhn.fhir.rest.client.api.IRestfulClient; 045import ca.uhn.fhir.rest.client.api.IRestfulClientFactory; 046import ca.uhn.fhir.system.HapiSystemProperties; 047import ca.uhn.fhir.util.FhirTerser; 048import ca.uhn.fhir.util.ReflectionUtil; 049import ca.uhn.fhir.util.VersionUtil; 050import ca.uhn.fhir.validation.FhirValidator; 051import org.apache.commons.lang3.Validate; 052import org.apache.commons.lang3.exception.ExceptionUtils; 053import org.apache.jena.riot.Lang; 054import org.hl7.fhir.instance.model.api.IBase; 055import org.hl7.fhir.instance.model.api.IBaseBundle; 056import org.hl7.fhir.instance.model.api.IBaseResource; 057import org.hl7.fhir.instance.model.api.IPrimitiveType; 058 059import javax.annotation.Nonnull; 060import javax.annotation.Nullable; 061import java.io.IOException; 062import java.io.InputStream; 063import java.lang.reflect.Method; 064import java.lang.reflect.Modifier; 065import java.util.ArrayList; 066import java.util.Arrays; 067import java.util.Collection; 068import java.util.Collections; 069import java.util.EnumMap; 070import java.util.Enumeration; 071import java.util.HashMap; 072import java.util.HashSet; 073import java.util.List; 074import java.util.Map; 075import java.util.Map.Entry; 076import java.util.Properties; 077import java.util.Set; 078 079/** 080 * The FHIR context is the central starting point for the use of the HAPI FHIR API. It should be created once, and then 081 * used as a factory for various other types of objects (parsers, clients, etc.). 082 * 083 * <p> 084 * Important usage notes: 085 * </p> 086 * <ul> 087 * <li> 088 * Thread safety: <b>This class is thread safe</b> and may be shared between multiple processing 089 * threads, except for the {@link #registerCustomType} and {@link #registerCustomTypes} methods. 090 * </li> 091 * <li> 092 * Performance: <b>This class is expensive</b> to create, as it scans every resource class it needs to parse or encode 093 * to build up an internal model of those classes. For that reason, you should try to create one FhirContext instance 094 * which remains for the life of your application and reuse that instance. Note that it will not cause problems to 095 * create multiple instances (ie. resources originating from one FhirContext may be passed to parsers originating from 096 * another) but you will incur a performance penalty if a new FhirContext is created for every message you parse/encode. 097 * </li> 098 * </ul> 099 */ 100public class FhirContext { 101 102 private static final List<Class<? extends IBaseResource>> EMPTY_LIST = Collections.emptyList(); 103 private static final Map<FhirVersionEnum, FhirContext> ourStaticContexts = Collections.synchronizedMap(new EnumMap<>(FhirVersionEnum.class)); 104 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContext.class); 105 private final IFhirVersion myVersion; 106 private final Map<String, Class<? extends IBaseResource>> myDefaultTypeForProfile = new HashMap<>(); 107 private final Set<PerformanceOptionsEnum> myPerformanceOptions = new HashSet<>(); 108 private final Collection<Class<? extends IBaseResource>> myResourceTypesToScan; 109 private AddProfileTagEnum myAddProfileTagWhenEncoding = AddProfileTagEnum.ONLY_FOR_CUSTOM; 110 private volatile Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition = Collections.emptyMap(); 111 private ArrayList<Class<? extends IBase>> myCustomTypes; 112 private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap(); 113 private volatile boolean myInitialized; 114 private volatile boolean myInitializing = false; 115 private HapiLocalizer myLocalizer = new HapiLocalizer(); 116 private volatile Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinition = Collections.emptyMap(); 117 private volatile Map<String, RuntimeResourceDefinition> myNameToResourceDefinition = Collections.emptyMap(); 118 private volatile Map<String, Class<? extends IBaseResource>> myNameToResourceType; 119 private volatile INarrativeGenerator myNarrativeGenerator; 120 private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler(); 121 private ParserOptions myParserOptions = new ParserOptions(); 122 private volatile IRestfulClientFactory myRestfulClientFactory; 123 private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; 124 private IValidationSupport myValidationSupport; 125 private Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> myVersionToNameToResourceType = Collections.emptyMap(); 126 private volatile Set<String> myResourceNames; 127 private volatile Boolean myFormatXmlSupported; 128 private volatile Boolean myFormatJsonSupported; 129 private volatile Boolean myFormatNDJsonSupported; 130 private volatile Boolean myFormatRdfSupported; 131 private IFhirValidatorFactory myFhirValidatorFactory = FhirValidator::new; 132 133 /** 134 * @deprecated It is recommended that you use one of the static initializer methods instead 135 * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} 136 */ 137 @Deprecated 138 public FhirContext() { 139 this(EMPTY_LIST); 140 } 141 142 /** 143 * @deprecated It is recommended that you use one of the static initializer methods instead 144 * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} 145 */ 146 @Deprecated 147 public FhirContext(final Class<? extends IBaseResource> theResourceType) { 148 this(toCollection(theResourceType)); 149 } 150 151 /** 152 * @deprecated It is recommended that you use one of the static initializer methods instead 153 * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} 154 */ 155 @Deprecated 156 public FhirContext(final Class<?>... theResourceTypes) { 157 this(toCollection(theResourceTypes)); 158 } 159 160 /** 161 * @deprecated It is recommended that you use one of the static initializer methods instead 162 * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} 163 */ 164 @Deprecated 165 public FhirContext(final Collection<Class<? extends IBaseResource>> theResourceTypes) { 166 this(null, theResourceTypes); 167 } 168 169 /** 170 * In most cases it is recommended that you use one of the static initializer methods instead 171 * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()}, but 172 * this method can also be used if you wish to supply the version programmatically. 173 */ 174 public FhirContext(final FhirVersionEnum theVersion) { 175 this(theVersion, null); 176 } 177 178 private FhirContext(final FhirVersionEnum theVersion, final Collection<Class<? extends IBaseResource>> theResourceTypes) { 179 VersionUtil.getVersion(); 180 181 if (theVersion != null) { 182 if (!theVersion.isPresentOnClasspath()) { 183 throw new IllegalStateException(Msg.code(1680) + getLocalizer().getMessage(FhirContext.class, "noStructuresForSpecifiedVersion", theVersion.name())); 184 } 185 myVersion = theVersion.getVersionImplementation(); 186 } else if (FhirVersionEnum.DSTU2.isPresentOnClasspath()) { 187 myVersion = FhirVersionEnum.DSTU2.getVersionImplementation(); 188 } else if (FhirVersionEnum.DSTU2_HL7ORG.isPresentOnClasspath()) { 189 myVersion = FhirVersionEnum.DSTU2_HL7ORG.getVersionImplementation(); 190 } else if (FhirVersionEnum.DSTU2_1.isPresentOnClasspath()) { 191 myVersion = FhirVersionEnum.DSTU2_1.getVersionImplementation(); 192 } else if (FhirVersionEnum.DSTU3.isPresentOnClasspath()) { 193 myVersion = FhirVersionEnum.DSTU3.getVersionImplementation(); 194 } else if (FhirVersionEnum.R4.isPresentOnClasspath()) { 195 myVersion = FhirVersionEnum.R4.getVersionImplementation(); 196 } else if (FhirVersionEnum.R4B.isPresentOnClasspath()) { 197 myVersion = FhirVersionEnum.R4B.getVersionImplementation(); 198 } else { 199 throw new IllegalStateException(Msg.code(1681) + getLocalizer().getMessage(FhirContext.class, "noStructures")); 200 } 201 202 if (theVersion == null) { 203 ourLog.info("Creating new FhirContext with auto-detected version [{}]. It is recommended to explicitly select a version for future compatibility by invoking FhirContext.forDstuX()", 204 myVersion.getVersion().name()); 205 } else { 206 if (HapiSystemProperties.isUnitTestModeEnabled()) { 207 String calledAt = ExceptionUtils.getStackFrames(new Throwable())[4]; 208 ourLog.info("Creating new FHIR context for FHIR version [{}]{}", myVersion.getVersion().name(), calledAt); 209 } else { 210 ourLog.info("Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name()); 211 } 212 } 213 214 myResourceTypesToScan = theResourceTypes; 215 216 /* 217 * Check if we're running in Android mode and configure the context appropriately if so 218 */ 219 try { 220 Class<?> clazz = Class.forName("ca.uhn.fhir.android.AndroidMarker"); 221 ourLog.info("Android mode detected, configuring FhirContext for Android operation"); 222 try { 223 Method method = clazz.getMethod("configureContext", FhirContext.class); 224 method.invoke(null, this); 225 } catch (Throwable e) { 226 ourLog.warn("Failed to configure context for Android operation", e); 227 } 228 } catch (ClassNotFoundException e) { 229 ourLog.trace("Android mode not detected"); 230 } 231 232 } 233 234 235 /** 236 * @since 5.6.0 237 */ 238 public static FhirContext forDstu2Cached() { 239 return forCached(FhirVersionEnum.DSTU2); 240 } 241 242 /** 243 * @since 6.2.0 244 */ 245 public static FhirContext forDstu2Hl7OrgCached() { 246 return forCached(FhirVersionEnum.DSTU2_HL7ORG); 247 } 248 249 250 /** 251 * @since 5.5.0 252 */ 253 public static FhirContext forDstu3Cached() { 254 return forCached(FhirVersionEnum.DSTU3); 255 } 256 257 /** 258 * @since 5.5.0 259 */ 260 public static FhirContext forR4Cached() { 261 return forCached(FhirVersionEnum.R4); 262 } 263 264 /** 265 * @since 6.1.0 266 */ 267 public static FhirContext forR4BCached() { 268 return forCached(FhirVersionEnum.R4B); 269 } 270 271 /** 272 * @since 5.5.0 273 */ 274 public static FhirContext forR5Cached() { 275 return forCached(FhirVersionEnum.R5); 276 } 277 278 private String createUnknownResourceNameError(final String theResourceName, final FhirVersionEnum theVersion) { 279 return getLocalizer().getMessage(FhirContext.class, "unknownResourceName", theResourceName, theVersion); 280 } 281 282 private void ensureCustomTypeList() { 283 myClassToElementDefinition.clear(); 284 if (myCustomTypes == null) { 285 myCustomTypes = new ArrayList<>(); 286 } 287 } 288 289 /** 290 * When encoding resources, this setting configures the parser to include 291 * an entry in the resource's metadata section which indicates which profile(s) the 292 * resource claims to conform to. The default is {@link AddProfileTagEnum#ONLY_FOR_CUSTOM}. 293 * 294 * @see #setAddProfileTagWhenEncoding(AddProfileTagEnum) for more information 295 */ 296 public AddProfileTagEnum getAddProfileTagWhenEncoding() { 297 return myAddProfileTagWhenEncoding; 298 } 299 300 /** 301 * When encoding resources, this setting configures the parser to include 302 * an entry in the resource's metadata section which indicates which profile(s) the 303 * resource claims to conform to. The default is {@link AddProfileTagEnum#ONLY_FOR_CUSTOM}. 304 * <p> 305 * This feature is intended for situations where custom resource types are being used, 306 * avoiding the need to manually add profile declarations for these custom types. 307 * </p> 308 * <p> 309 * See <a href="http://jamesagnew.gihhub.io/hapi-fhir/doc_extensions.html">Profiling and Extensions</a> 310 * for more information on using custom types. 311 * </p> 312 * <p> 313 * Note that this feature automatically adds the profile, but leaves any profile tags 314 * which have been manually added in place as well. 315 * </p> 316 * 317 * @param theAddProfileTagWhenEncoding The add profile mode (must not be <code>null</code>) 318 */ 319 public void setAddProfileTagWhenEncoding(final AddProfileTagEnum theAddProfileTagWhenEncoding) { 320 Validate.notNull(theAddProfileTagWhenEncoding, "theAddProfileTagWhenEncoding must not be null"); 321 myAddProfileTagWhenEncoding = theAddProfileTagWhenEncoding; 322 } 323 324 Collection<RuntimeResourceDefinition> getAllResourceDefinitions() { 325 validateInitialized(); 326 return myNameToResourceDefinition.values(); 327 } 328 329 /** 330 * Returns the default resource type for the given profile 331 * 332 * @see #setDefaultTypeForProfile(String, Class) 333 */ 334 public Class<? extends IBaseResource> getDefaultTypeForProfile(final String theProfile) { 335 validateInitialized(); 336 return myDefaultTypeForProfile.get(theProfile); 337 } 338 339 /** 340 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed 341 * for extending the core library. 342 */ 343 @SuppressWarnings("unchecked") 344 public BaseRuntimeElementDefinition<?> getElementDefinition(final Class<? extends IBase> theElementType) { 345 validateInitialized(); 346 BaseRuntimeElementDefinition<?> retVal = myClassToElementDefinition.get(theElementType); 347 if (retVal == null) { 348 retVal = scanDatatype((Class<? extends IElement>) theElementType); 349 } 350 return retVal; 351 } 352 353 /** 354 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed 355 * for extending the core library. 356 * <p> 357 * Note that this method is case insensitive! 358 * </p> 359 */ 360 @Nullable 361 public BaseRuntimeElementDefinition<?> getElementDefinition(final String theElementName) { 362 validateInitialized(); 363 return myNameToElementDefinition.get(theElementName.toLowerCase()); 364 } 365 366 /** 367 * Returns all element definitions (resources, datatypes, etc.) 368 */ 369 public Collection<BaseRuntimeElementDefinition<?>> getElementDefinitions() { 370 validateInitialized(); 371 return Collections.unmodifiableCollection(myClassToElementDefinition.values()); 372 } 373 374 /** 375 * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with 376 * caution 377 */ 378 public HapiLocalizer getLocalizer() { 379 if (myLocalizer == null) { 380 myLocalizer = new HapiLocalizer(); 381 } 382 return myLocalizer; 383 } 384 385 /** 386 * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with 387 * caution 388 */ 389 public void setLocalizer(final HapiLocalizer theMessages) { 390 myLocalizer = theMessages; 391 } 392 393 public INarrativeGenerator getNarrativeGenerator() { 394 return myNarrativeGenerator; 395 } 396 397 public FhirContext setNarrativeGenerator(final INarrativeGenerator theNarrativeGenerator) { 398 myNarrativeGenerator = theNarrativeGenerator; 399 return this; 400 } 401 402 /** 403 * Returns the parser options object which will be used to supply default 404 * options to newly created parsers 405 * 406 * @return The parser options - Will not return <code>null</code> 407 */ 408 public ParserOptions getParserOptions() { 409 return myParserOptions; 410 } 411 412 /** 413 * Sets the parser options object which will be used to supply default 414 * options to newly created parsers 415 * 416 * @param theParserOptions The parser options object - Must not be <code>null</code> 417 */ 418 public void setParserOptions(final ParserOptions theParserOptions) { 419 Validate.notNull(theParserOptions, "theParserOptions must not be null"); 420 myParserOptions = theParserOptions; 421 } 422 423 /** 424 * Get the configured performance options 425 */ 426 public Set<PerformanceOptionsEnum> getPerformanceOptions() { 427 return myPerformanceOptions; 428 } 429 430 // /** 431 // * Return an unmodifiable collection containing all known resource definitions 432 // */ 433 // public Collection<RuntimeResourceDefinition> getResourceDefinitions() { 434 // 435 // Set<Class<? extends IBase>> datatypes = Collections.emptySet(); 436 // Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> existing = Collections.emptyMap(); 437 // HashMap<String, Class<? extends IBaseResource>> types = new HashMap<String, Class<? extends IBaseResource>>(); 438 // ModelScanner.scanVersionPropertyFile(datatypes, types, myVersion.getVersion(), existing); 439 // for (int next : types.) 440 // 441 // return Collections.unmodifiableCollection(myIdToResourceDefinition.values()); 442 // } 443 444 /** 445 * Sets the configured performance options 446 * 447 * @see PerformanceOptionsEnum for a list of available options 448 */ 449 public void setPerformanceOptions(final Collection<PerformanceOptionsEnum> theOptions) { 450 myPerformanceOptions.clear(); 451 if (theOptions != null) { 452 myPerformanceOptions.addAll(theOptions); 453 } 454 } 455 456 /** 457 * Sets the configured performance options 458 * 459 * @see PerformanceOptionsEnum for a list of available options 460 */ 461 public void setPerformanceOptions(final PerformanceOptionsEnum... thePerformanceOptions) { 462 Collection<PerformanceOptionsEnum> asList = null; 463 if (thePerformanceOptions != null) { 464 asList = Arrays.asList(thePerformanceOptions); 465 } 466 setPerformanceOptions(asList); 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 getResourceDefinition(final Class<? extends IBaseResource> theResourceType) { 474 validateInitialized(); 475 Validate.notNull(theResourceType, "theResourceType can not be null"); 476 477 if (Modifier.isAbstract(theResourceType.getModifiers())) { 478 throw new IllegalArgumentException(Msg.code(1682) + "Can not scan abstract or interface class (resource definitions must be concrete classes): " + theResourceType.getName()); 479 } 480 481 RuntimeResourceDefinition retVal = (RuntimeResourceDefinition) myClassToElementDefinition.get(theResourceType); 482 if (retVal == null) { 483 retVal = scanResourceType(theResourceType); 484 } 485 486 return retVal; 487 } 488 489 public RuntimeResourceDefinition getResourceDefinition(final FhirVersionEnum theVersion, final String theResourceName) { 490 Validate.notNull(theVersion, "theVersion can not be null"); 491 validateInitialized(); 492 493 if (theVersion.equals(myVersion.getVersion())) { 494 return getResourceDefinition(theResourceName); 495 } 496 497 Map<String, Class<? extends IBaseResource>> nameToType = myVersionToNameToResourceType.get(theVersion); 498 if (nameToType == null) { 499 nameToType = new HashMap<>(); 500 Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> existing = new HashMap<>(); 501 ModelScanner.scanVersionPropertyFile(null, nameToType, theVersion, existing); 502 503 Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> newVersionToNameToResourceType = new HashMap<>(); 504 newVersionToNameToResourceType.putAll(myVersionToNameToResourceType); 505 newVersionToNameToResourceType.put(theVersion, nameToType); 506 myVersionToNameToResourceType = newVersionToNameToResourceType; 507 } 508 509 Class<? extends IBaseResource> resourceType = nameToType.get(theResourceName.toLowerCase()); 510 if (resourceType == null) { 511 throw new DataFormatException(Msg.code(1683) + createUnknownResourceNameError(theResourceName, theVersion)); 512 } 513 514 return getResourceDefinition(resourceType); 515 } 516 517 /** 518 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed 519 * for extending the core library. 520 */ 521 public RuntimeResourceDefinition getResourceDefinition(final IBaseResource theResource) { 522 validateInitialized(); 523 Validate.notNull(theResource, "theResource must not be null"); 524 return getResourceDefinition(theResource.getClass()); 525 } 526 527 /** 528 * Returns the name of a given resource class. 529 */ 530 public String getResourceType(final Class<? extends IBaseResource> theResourceType) { 531 return getResourceDefinition(theResourceType).getName(); 532 } 533 534 /** 535 * Returns the name of the scanned runtime model for the given type. This is an advanced feature which is generally only needed 536 * for extending the core library. 537 */ 538 public String getResourceType(final IBaseResource theResource) { 539 return getResourceDefinition(theResource).getName(); 540 } 541 542 /* 543 * Returns the type of the scanned runtime model for the given type. This is an advanced feature which is generally only needed 544 * for extending the core library. 545 * <p> 546 * Note that this method is case insensitive! 547 * </p> 548 * 549 * @throws DataFormatException If the resource name is not known 550 */ 551 public String getResourceType(final String theResourceName) throws DataFormatException { 552 return getResourceDefinition(theResourceName).getName(); 553 } 554 555 /* 556 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed 557 * for extending the core library. 558 * <p> 559 * Note that this method is case insensitive! 560 * </p> 561 * 562 * @throws DataFormatException If the resource name is not known 563 */ 564 public RuntimeResourceDefinition getResourceDefinition(final String theResourceName) throws DataFormatException { 565 validateInitialized(); 566 Validate.notBlank(theResourceName, "theResourceName must not be blank"); 567 568 String resourceName = theResourceName.toLowerCase(); 569 RuntimeResourceDefinition retVal = myNameToResourceDefinition.get(resourceName); 570 571 if (retVal == null) { 572 Class<? extends IBaseResource> clazz = myNameToResourceType.get(resourceName.toLowerCase()); 573 if (clazz == null) { 574 // *********************************************************************** 575 // Multiple spots in HAPI FHIR and Smile CDR depend on DataFormatException 576 // being thrown by this method, don't change that. 577 // *********************************************************************** 578 throw new DataFormatException(Msg.code(1684) + createUnknownResourceNameError(theResourceName, myVersion.getVersion())); 579 } 580 if (IBaseResource.class.isAssignableFrom(clazz)) { 581 retVal = scanResourceType(clazz); 582 } 583 } 584 return retVal; 585 } 586 587 /** 588 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed 589 * for extending the core library. 590 */ 591 public RuntimeResourceDefinition getResourceDefinitionById(final String theId) { 592 validateInitialized(); 593 return myIdToResourceDefinition.get(theId); 594 } 595 596 /** 597 * Returns the scanned runtime models. This is an advanced feature which is generally only needed for extending the 598 * core library. 599 */ 600 public Collection<RuntimeResourceDefinition> getResourceDefinitionsWithExplicitId() { 601 validateInitialized(); 602 return myIdToResourceDefinition.values(); 603 } 604 605 /** 606 * Returns an unmodifiable set containing all resource names known to this 607 * context 608 * 609 * @since 5.1.0 610 */ 611 public Set<String> getResourceTypes() { 612 Set<String> resourceNames = myResourceNames; 613 if (resourceNames == null) { 614 resourceNames = buildResourceNames(); 615 myResourceNames = resourceNames; 616 } 617 return resourceNames; 618 } 619 620 @Nonnull 621 private Set<String> buildResourceNames() { 622 Set<String> retVal = new HashSet<>(); 623 Properties props = new Properties(); 624 try (InputStream propFile = myVersion.getFhirVersionPropertiesFile()) { 625 props.load(propFile); 626 } catch (IOException e) { 627 throw new ConfigurationException(Msg.code(1685) + "Failed to load version properties file", e); 628 } 629 Enumeration<?> propNames = props.propertyNames(); 630 while (propNames.hasMoreElements()) { 631 String next = (String) propNames.nextElement(); 632 if (next.startsWith("resource.")) { 633 retVal.add(next.substring("resource.".length()).trim()); 634 } 635 } 636 return retVal; 637 } 638 639 /** 640 * Get the restful client factory. If no factory has been set, this will be initialized with 641 * a new ApacheRestfulClientFactory. 642 * 643 * @return the factory used to create the restful clients 644 */ 645 public IRestfulClientFactory getRestfulClientFactory() { 646 if (myRestfulClientFactory == null) { 647 try { 648 myRestfulClientFactory = (IRestfulClientFactory) ReflectionUtil.newInstance(Class.forName("ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory"), FhirContext.class, this); 649 } catch (ClassNotFoundException e) { 650 throw new ConfigurationException(Msg.code(1686) + "hapi-fhir-client does not appear to be on the classpath"); 651 } 652 } 653 return myRestfulClientFactory; 654 } 655 656 /** 657 * Set the restful client factory 658 * 659 * @param theRestfulClientFactory The new client factory (must not be null) 660 */ 661 public void setRestfulClientFactory(final IRestfulClientFactory theRestfulClientFactory) { 662 Validate.notNull(theRestfulClientFactory, "theRestfulClientFactory must not be null"); 663 this.myRestfulClientFactory = theRestfulClientFactory; 664 } 665 666 public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() { 667 validateInitialized(); 668 return myRuntimeChildUndeclaredExtensionDefinition; 669 } 670 671 /** 672 * Returns the validation support module configured for this context, creating a default 673 * implementation if no module has been passed in via the {@link #setValidationSupport(IValidationSupport)} 674 * method 675 * 676 * @see #setValidationSupport(IValidationSupport) 677 */ 678 public IValidationSupport getValidationSupport() { 679 IValidationSupport retVal = myValidationSupport; 680 if (retVal == null) { 681 retVal = new DefaultProfileValidationSupport(this); 682 683 /* 684 * If hapi-fhir-validation is on the classpath, we can create a much more robust 685 * validation chain using the classes found in that package 686 */ 687 String inMemoryTermSvcType = "org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport"; 688 String commonCodeSystemsSupportType = "org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService"; 689 if (ReflectionUtil.typeExists(inMemoryTermSvcType)) { 690 IValidationSupport inMemoryTermSvc = ReflectionUtil.newInstanceOrReturnNull(inMemoryTermSvcType, IValidationSupport.class, new Class<?>[]{FhirContext.class}, new Object[]{this}); 691 IValidationSupport commonCodeSystemsSupport = ReflectionUtil.newInstanceOrReturnNull(commonCodeSystemsSupportType, IValidationSupport.class, new Class<?>[]{FhirContext.class}, new Object[]{this}); 692 retVal = ReflectionUtil.newInstanceOrReturnNull("org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain", IValidationSupport.class, new Class<?>[]{IValidationSupport[].class}, new Object[]{new IValidationSupport[]{ 693 retVal, 694 inMemoryTermSvc, 695 commonCodeSystemsSupport 696 }}); 697 assert retVal != null : "Failed to instantiate " + "org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain"; 698 } 699 700 701 myValidationSupport = retVal; 702 } 703 return retVal; 704 } 705 706 /** 707 * Sets the validation support module to use for this context. The validation support module 708 * is used to supply underlying infrastructure such as conformance resources (StructureDefinition, ValueSet, etc) 709 * as well as to provide terminology services to modules such as the validator and FluentPath executor 710 */ 711 public void setValidationSupport(IValidationSupport theValidationSupport) { 712 myValidationSupport = theValidationSupport; 713 } 714 715 public IFhirVersion getVersion() { 716 return myVersion; 717 } 718 719 /** 720 * Returns <code>true</code> if any default types for specific profiles have been defined 721 * within this context. 722 * 723 * @see #setDefaultTypeForProfile(String, Class) 724 * @see #getDefaultTypeForProfile(String) 725 */ 726 public boolean hasDefaultTypeForProfile() { 727 validateInitialized(); 728 return !myDefaultTypeForProfile.isEmpty(); 729 } 730 731 /** 732 * @return Returns <code>true</code> if the XML serialization format is supported, based on the 733 * available libraries on the classpath. 734 * 735 * @since 5.4.0 736 */ 737 public boolean isFormatXmlSupported() { 738 Boolean retVal = myFormatXmlSupported; 739 if (retVal == null) { 740 retVal = tryToInitParser(() -> newXmlParser()); 741 myFormatXmlSupported = retVal; 742 } 743 return retVal; 744 } 745 746 /** 747 * @return Returns <code>true</code> if the JSON serialization format is supported, based on the 748 * available libraries on the classpath. 749 * 750 * @since 5.4.0 751 */ 752 public boolean isFormatJsonSupported() { 753 Boolean retVal = myFormatJsonSupported; 754 if (retVal == null) { 755 retVal = tryToInitParser(() -> newJsonParser()); 756 myFormatJsonSupported = retVal; 757 } 758 return retVal; 759 } 760 761 /** 762 * @return Returns <code>true</code> if the NDJSON serialization format is supported, based on the 763 * available libraries on the classpath. 764 * 765 * @since 5.6.0 766 */ 767 public boolean isFormatNDJsonSupported() { 768 Boolean retVal = myFormatNDJsonSupported; 769 if (retVal == null) { 770 retVal = tryToInitParser(() -> newNDJsonParser()); 771 myFormatNDJsonSupported = retVal; 772 } 773 return retVal; 774 } 775 776 /** 777 * @return Returns <code>true</code> if the RDF serialization format is supported, based on the 778 * available libraries on the classpath. 779 * 780 * @since 5.4.0 781 */ 782 public boolean isFormatRdfSupported() { 783 Boolean retVal = myFormatRdfSupported; 784 if (retVal == null) { 785 retVal = tryToInitParser(() -> newRDFParser()); 786 myFormatRdfSupported = retVal; 787 } 788 return retVal; 789 } 790 791 public IVersionSpecificBundleFactory newBundleFactory() { 792 return myVersion.newBundleFactory(this); 793 } 794 795 /** 796 * @since 2.2 797 * @deprecated Deprecated in HAPI FHIR 5.0.0. Use {@link #newFhirPath()} instead. 798 */ 799 @Deprecated 800 public IFhirPath newFluentPath() { 801 return newFhirPath(); 802 } 803 804 /** 805 * Creates a new FhirPath engine which can be used to evaluate 806 * path expressions over FHIR resources. Note that this engine will use the 807 * {@link IValidationSupport context validation support} module which is 808 * configured on the context at the time this method is called. 809 * <p> 810 * In other words, you may wish to call {@link #setValidationSupport(IValidationSupport)} before 811 * calling {@link #newFluentPath()} 812 * </p> 813 * <p> 814 * Note that this feature was added for FHIR DSTU3 and is not available 815 * for contexts configured to use an older version of FHIR. Calling this method 816 * on a context for a previous version of fhir will result in an 817 * {@link UnsupportedOperationException} 818 * </p> 819 * 820 * @since 5.0.0 821 */ 822 public IFhirPath newFhirPath() { 823 return myVersion.createFhirPathExecutor(this); 824 } 825 826 /** 827 * Create and return a new JSON parser. 828 * 829 * <p> 830 * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread 831 * or every message being parsed/encoded. 832 * </p> 833 * <p> 834 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed 835 * without incurring any performance penalty 836 * </p> 837 */ 838 public IParser newJsonParser() { 839 return new JsonParser(this, myParserErrorHandler); 840 } 841 842 /** 843 * Create and return a new NDJSON parser. 844 * 845 * <p> 846 * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread 847 * or every message being parsed/encoded. 848 * </p> 849 * <p> 850 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed 851 * without incurring any performance penalty 852 * </p> 853 * <p> 854 * The NDJsonParser provided here is expected to translate between legal NDJson and FHIR Bundles. 855 * In particular, it is able to encode the resources in a FHIR Bundle to NDJson, as well as decode 856 * NDJson into a FHIR "collection"-type Bundle populated with the resources described in the NDJson. 857 * It will throw an exception in the event where it is asked to encode to anything other than a FHIR Bundle 858 * or where it is asked to decode into anything other than a FHIR Bundle. 859 * </p> 860 */ 861 public IParser newNDJsonParser() { 862 return new NDJsonParser(this, myParserErrorHandler); 863 } 864 865 /** 866 * Create and return a new RDF parser. 867 * 868 * <p> 869 * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread 870 * or every message being parsed/encoded. 871 * </p> 872 * <p> 873 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed 874 * without incurring any performance penalty 875 * </p> 876 */ 877 public IParser newRDFParser() { 878 return new RDFParser(this, myParserErrorHandler, Lang.TURTLE); 879 } 880 881 /** 882 * Instantiates a new client instance. This method requires an interface which is defined specifically for your use 883 * cases to contain methods for each of the RESTful operations you wish to implement (e.g. "read ImagingStudy", 884 * "search Patient by identifier", etc.). This interface must extend {@link IRestfulClient} (or commonly its 885 * sub-interface {@link IBasicClient}). See the <a 886 * href="https://hapifhir.io/hapi-fhir/docs/client/introduction.html">RESTful Client</a> documentation for more 887 * information on how to define this interface. 888 * 889 * <p> 890 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every operation invocation 891 * without incurring any performance penalty 892 * </p> 893 * 894 * @param theClientType The client type, which is an interface type to be instantiated 895 * @param theServerBase The URL of the base for the restful FHIR server to connect to 896 * @return A newly created client 897 * @throws ConfigurationException If the interface type is not an interface 898 */ 899 public <T extends IRestfulClient> T newRestfulClient(final Class<T> theClientType, final String theServerBase) { 900 return getRestfulClientFactory().newClient(theClientType, theServerBase); 901 } 902 903 /** 904 * Instantiates a new generic client. A generic client is able to perform any of the FHIR RESTful operations against 905 * a compliant server, but does not have methods defining the specific functionality required (as is the case with 906 * {@link #newRestfulClient(Class, String) non-generic clients}). 907 * 908 * <p> 909 * Performance Note: This method performs an additional GET request to /metadata before 910 * the desired request is performed. 911 * </p> 912 * 913 * @param theServerBase The URL of the base for the restful FHIR server to connect to 914 */ 915 public IGenericClient newRestfulGenericClient(final String theServerBase) { 916 return getRestfulClientFactory().newGenericClient(theServerBase); 917 } 918 919 public FhirTerser newTerser() { 920 return new FhirTerser(this); 921 } 922 923 /** 924 * Create a new validator instance. 925 * <p> 926 * Note on thread safety: Validators are thread safe, you may use a single validator 927 * in multiple threads. (This is in contrast to parsers) 928 * </p> 929 */ 930 public FhirValidator newValidator() { 931 return myFhirValidatorFactory.newFhirValidator(this); 932 } 933 934 public ViewGenerator newViewGenerator() { 935 return new ViewGenerator(this); 936 } 937 938 /** 939 * Create and return a new XML parser. 940 * 941 * <p> 942 * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread 943 * or every message being parsed/encoded. 944 * </p> 945 * <p> 946 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed 947 * without incurring any performance penalty 948 * </p> 949 */ 950 public IParser newXmlParser() { 951 return new XmlParser(this, myParserErrorHandler); 952 } 953 954 /** 955 * This method may be used to register a custom resource or datatype. Note that by using 956 * custom types, you are creating a system that will not interoperate with other systems that 957 * do not know about your custom type. There are valid reasons however for wanting to create 958 * custom types and this method can be used to enable them. 959 * <p> 960 * <b>THREAD SAFETY WARNING:</b> This method is not thread safe. It should be called before any 961 * threads are able to call any methods on this context. 962 * </p> 963 * 964 * @param theType The custom type to add (must not be <code>null</code>) 965 */ 966 public void registerCustomType(final Class<? extends IBase> theType) { 967 Validate.notNull(theType, "theType must not be null"); 968 969 ensureCustomTypeList(); 970 myCustomTypes.add(theType); 971 } 972 973 /** 974 * This method may be used to register a custom resource or datatype. Note that by using 975 * custom types, you are creating a system that will not interoperate with other systems that 976 * do not know about your custom type. There are valid reasons however for wanting to create 977 * custom types and this method can be used to enable them. 978 * <p> 979 * <b>THREAD SAFETY WARNING:</b> This method is not thread safe. It should be called before any 980 * threads are able to call any methods on this context. 981 * </p> 982 * 983 * @param theTypes The custom types to add (must not be <code>null</code> or contain null elements in the collection) 984 */ 985 public void registerCustomTypes(final Collection<Class<? extends IBase>> theTypes) { 986 Validate.notNull(theTypes, "theTypes must not be null"); 987 Validate.noNullElements(theTypes.toArray(), "theTypes must not contain any null elements"); 988 989 ensureCustomTypeList(); 990 991 myCustomTypes.addAll(theTypes); 992 } 993 994 private BaseRuntimeElementDefinition<?> scanDatatype(final Class<? extends IElement> theResourceType) { 995 ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<>(); 996 resourceTypes.add(theResourceType); 997 Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> defs = scanResourceTypes(resourceTypes); 998 return defs.get(theResourceType); 999 } 1000 1001 private RuntimeResourceDefinition scanResourceType(final Class<? extends IBaseResource> theResourceType) { 1002 ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<>(); 1003 resourceTypes.add(theResourceType); 1004 Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> defs = scanResourceTypes(resourceTypes); 1005 return (RuntimeResourceDefinition) defs.get(theResourceType); 1006 } 1007 1008 private synchronized Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> scanResourceTypes(final Collection<Class<? extends IElement>> theResourceTypes) { 1009 List<Class<? extends IBase>> typesToScan = new ArrayList<>(); 1010 if (theResourceTypes != null) { 1011 typesToScan.addAll(theResourceTypes); 1012 } 1013 if (myCustomTypes != null) { 1014 typesToScan.addAll(myCustomTypes); 1015 myCustomTypes = null; 1016 } 1017 1018 ModelScanner scanner = new ModelScanner(this, myVersion.getVersion(), myClassToElementDefinition, typesToScan); 1019 if (myRuntimeChildUndeclaredExtensionDefinition == null) { 1020 myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition(); 1021 } 1022 1023 Map<String, BaseRuntimeElementDefinition<?>> nameToElementDefinition = new HashMap<>(); 1024 nameToElementDefinition.putAll(myNameToElementDefinition); 1025 for (Entry<String, BaseRuntimeElementDefinition<?>> next : scanner.getNameToElementDefinitions().entrySet()) { 1026 if (!nameToElementDefinition.containsKey(next.getKey())) { 1027 nameToElementDefinition.put(next.getKey().toLowerCase(), next.getValue()); 1028 } 1029 } 1030 1031 Map<String, RuntimeResourceDefinition> nameToResourceDefinition = new HashMap<>(); 1032 nameToResourceDefinition.putAll(myNameToResourceDefinition); 1033 for (Entry<String, RuntimeResourceDefinition> next : scanner.getNameToResourceDefinition().entrySet()) { 1034 if (!nameToResourceDefinition.containsKey(next.getKey())) { 1035 nameToResourceDefinition.put(next.getKey(), next.getValue()); 1036 } 1037 } 1038 1039 Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> classToElementDefinition = new HashMap<>(); 1040 classToElementDefinition.putAll(myClassToElementDefinition); 1041 classToElementDefinition.putAll(scanner.getClassToElementDefinitions()); 1042 for (BaseRuntimeElementDefinition<?> next : classToElementDefinition.values()) { 1043 if (next instanceof RuntimeResourceDefinition) { 1044 if ("Bundle".equals(next.getName())) { 1045 if (!IBaseBundle.class.isAssignableFrom(next.getImplementingClass())) { 1046 throw new ConfigurationException(Msg.code(1687) + "Resource type declares resource name Bundle but does not implement IBaseBundle"); 1047 } 1048 } 1049 } 1050 } 1051 1052 Map<String, RuntimeResourceDefinition> idToElementDefinition = new HashMap<>(); 1053 idToElementDefinition.putAll(myIdToResourceDefinition); 1054 idToElementDefinition.putAll(scanner.getIdToResourceDefinition()); 1055 1056 myNameToElementDefinition = nameToElementDefinition; 1057 myClassToElementDefinition = classToElementDefinition; 1058 myIdToResourceDefinition = idToElementDefinition; 1059 myNameToResourceDefinition = nameToResourceDefinition; 1060 1061 myNameToResourceType = scanner.getNameToResourceType(); 1062 1063 myInitialized = true; 1064 return classToElementDefinition; 1065 } 1066 1067 /** 1068 * Sets the default type which will be used when parsing a resource that is found to be 1069 * of the given profile. 1070 * <p> 1071 * For example, this method is invoked with the profile string of 1072 * <code>"http://example.com/some_patient_profile"</code> and the type of <code>MyPatient.class</code>, 1073 * if the parser is parsing a resource and finds that it declares that it conforms to that profile, 1074 * the <code>MyPatient</code> type will be used unless otherwise specified. 1075 * </p> 1076 * 1077 * @param theProfile The profile string, e.g. <code>"http://example.com/some_patient_profile"</code>. Must not be 1078 * <code>null</code> or empty. 1079 * @param theClass The resource type, or <code>null</code> to clear any existing type 1080 */ 1081 public void setDefaultTypeForProfile(final String theProfile, final Class<? extends IBaseResource> theClass) { 1082 Validate.notBlank(theProfile, "theProfile must not be null or empty"); 1083 if (theClass == null) { 1084 myDefaultTypeForProfile.remove(theProfile); 1085 } else { 1086 myDefaultTypeForProfile.put(theProfile, theClass); 1087 } 1088 } 1089 1090 /** 1091 * Sets a parser error handler to use by default on all parsers 1092 * 1093 * @param theParserErrorHandler The error handler 1094 */ 1095 public FhirContext setParserErrorHandler(final IParserErrorHandler theParserErrorHandler) { 1096 Validate.notNull(theParserErrorHandler, "theParserErrorHandler must not be null"); 1097 myParserErrorHandler = theParserErrorHandler; 1098 return this; 1099 } 1100 1101 /** 1102 * Set the factory method used to create FhirValidator instances 1103 * 1104 * @param theFhirValidatorFactory 1105 * @return this 1106 * @since 5.6.0 1107 */ 1108 public FhirContext setFhirValidatorFactory(IFhirValidatorFactory theFhirValidatorFactory) { 1109 myFhirValidatorFactory = theFhirValidatorFactory; 1110 return this; 1111 } 1112 1113 @SuppressWarnings({"cast"}) 1114 private List<Class<? extends IElement>> toElementList(final Collection<Class<? extends IBaseResource>> theResourceTypes) { 1115 if (theResourceTypes == null) { 1116 return null; 1117 } 1118 List<Class<? extends IElement>> resTypes = new ArrayList<>(); 1119 for (Class<? extends IBaseResource> next : theResourceTypes) { 1120 resTypes.add(next); 1121 } 1122 return resTypes; 1123 } 1124 1125 private void validateInitialized() { 1126 // See #610 1127 if (!myInitialized) { 1128 synchronized (this) { 1129 if (!myInitialized && !myInitializing) { 1130 myInitializing = true; 1131 scanResourceTypes(toElementList(myResourceTypesToScan)); 1132 } 1133 } 1134 } 1135 } 1136 1137 @Override 1138 public String toString() { 1139 return "FhirContext[" + myVersion.getVersion().name() + "]"; 1140 } 1141 1142 // TODO KHS add the other primitive types 1143 @Deprecated(since = "6.6.0", forRemoval = true) 1144 public IPrimitiveType<Boolean> getPrimitiveBoolean(Boolean theValue) { 1145 return newPrimitiveBoolean(theValue); 1146 } 1147 1148 public IPrimitiveType<Boolean> newPrimitiveBoolean(Boolean theValue) { 1149 IPrimitiveType<Boolean> retval = (IPrimitiveType<Boolean>) getElementDefinition("boolean").newInstance(); 1150 retval.setValue(theValue); 1151 return retval; 1152 } 1153 1154 public IPrimitiveType<String > newPrimitiveString(String theValue) { 1155 IPrimitiveType<String> retval = (IPrimitiveType<String>) getElementDefinition("string").newInstance(); 1156 retval.setValue(theValue); 1157 return retval; 1158 } 1159 1160 private static boolean tryToInitParser(Runnable run) { 1161 boolean retVal; 1162 try { 1163 run.run(); 1164 retVal = true; 1165 } catch (UnsupportedClassVersionError | Exception | NoClassDefFoundError e) { 1166 retVal = false; 1167 } 1168 return retVal; 1169 } 1170 1171 /** 1172 * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU2} 1173 */ 1174 public static FhirContext forDstu2() { 1175 return new FhirContext(FhirVersionEnum.DSTU2); 1176 } 1177 1178 /** 1179 * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2_HL7ORG DSTU2} (using the Reference 1180 * Implementation Structures) 1181 */ 1182 public static FhirContext forDstu2Hl7Org() { 1183 return new FhirContext(FhirVersionEnum.DSTU2_HL7ORG); 1184 } 1185 1186 /** 1187 * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU2} (2016 May DSTU3 Snapshot) 1188 */ 1189 public static FhirContext forDstu2_1() { 1190 return new FhirContext(FhirVersionEnum.DSTU2_1); 1191 } 1192 1193 /** 1194 * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU3 DSTU3} 1195 * 1196 * @since 1.4 1197 */ 1198 public static FhirContext forDstu3() { 1199 return new FhirContext(FhirVersionEnum.DSTU3); 1200 } 1201 1202 /** 1203 * Creates and returns a new FhirContext with version {@link FhirVersionEnum#R4 R4} 1204 * 1205 * @since 3.0.0 1206 */ 1207 public static FhirContext forR4() { 1208 return new FhirContext(FhirVersionEnum.R4); 1209 } 1210 1211 /** 1212 * Creates and returns a new FhirContext with version {@link FhirVersionEnum#R4B R4B} 1213 * 1214 * @since 6.2.0 1215 */ 1216 public static FhirContext forR4B() { 1217 return new FhirContext(FhirVersionEnum.R4B); 1218 } 1219 1220 /** 1221 * Creates and returns a new FhirContext with version {@link FhirVersionEnum#R5 R5} 1222 * 1223 * @since 4.0.0 1224 */ 1225 public static FhirContext forR5() { 1226 return new FhirContext(FhirVersionEnum.R5); 1227 } 1228 1229 /** 1230 * Returns a statically cached {@literal FhirContext} instance for the given version, creating one if none exists in the 1231 * cache. One FhirContext will be kept in the cache for each FHIR version that is requested (by calling 1232 * this method for that version), and the cache will never be expired. 1233 * 1234 * @since 5.1.0 1235 */ 1236 public static FhirContext forCached(FhirVersionEnum theFhirVersionEnum) { 1237 return ourStaticContexts.computeIfAbsent(theFhirVersionEnum, v -> new FhirContext(v)); 1238 } 1239 1240 private static Collection<Class<? extends IBaseResource>> toCollection(Class<? extends IBaseResource> theResourceType) { 1241 ArrayList<Class<? extends IBaseResource>> retVal = new ArrayList<>(1); 1242 retVal.add(theResourceType); 1243 return retVal; 1244 } 1245 1246 @SuppressWarnings("unchecked") 1247 private static List<Class<? extends IBaseResource>> toCollection(final Class<?>[] theResourceTypes) { 1248 ArrayList<Class<? extends IBaseResource>> retVal = new ArrayList<Class<? extends IBaseResource>>(1); 1249 for (Class<?> clazz : theResourceTypes) { 1250 if (!IResource.class.isAssignableFrom(clazz)) { 1251 throw new IllegalArgumentException(Msg.code(1688) + clazz.getCanonicalName() + " is not an instance of " + IResource.class.getSimpleName()); 1252 } 1253 retVal.add((Class<? extends IResource>) clazz); 1254 } 1255 return retVal; 1256 } 1257 1258}