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