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