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