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