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