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