001package ca.uhn.fhir.context; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2016 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import java.lang.reflect.Modifier; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.HashMap; 028import java.util.List; 029import java.util.Map; 030 031import org.apache.commons.lang3.Validate; 032import org.apache.commons.lang3.text.WordUtils; 033import org.hl7.fhir.instance.model.api.IBase; 034import org.hl7.fhir.instance.model.api.IBaseResource; 035 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.DataFormatException; 043import ca.uhn.fhir.parser.IParser; 044import ca.uhn.fhir.parser.IParserErrorHandler; 045import ca.uhn.fhir.parser.JsonParser; 046import ca.uhn.fhir.parser.LenientErrorHandler; 047import ca.uhn.fhir.parser.XmlParser; 048import ca.uhn.fhir.rest.client.IGenericClient; 049import ca.uhn.fhir.rest.client.IRestfulClientFactory; 050import ca.uhn.fhir.rest.client.RestfulClientFactory; 051import ca.uhn.fhir.rest.client.api.IBasicClient; 052import ca.uhn.fhir.rest.client.api.IRestfulClient; 053import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; 054import ca.uhn.fhir.util.FhirTerser; 055import ca.uhn.fhir.util.VersionUtil; 056import ca.uhn.fhir.validation.FhirValidator; 057 058/** 059 * The FHIR context is the central starting point for the use of the HAPI FHIR API. It should be created once, and then 060 * used as a factory for various other types of objects (parsers, clients, etc.). 061 * 062 * <p> 063 * Important usage notes: 064 * </p> 065 * <ul> 066 * <li>Thread safety: <b>This class is thread safe</b> and may be shared between multiple processing threads.</li> 067 * <li> 068 * Performance: <b>This class is expensive</b> to create, as it scans every resource class it needs to parse or encode 069 * to build up an internal model of those classes. For that reason, you should try to create one FhirContext instance 070 * which remains for the life of your application and reuse that instance. Note that it will not cause problems to 071 * create multiple instances (ie. resources originating from one FhirContext may be passed to parsers originating from 072 * another) but you will incur a performance penalty if a new FhirContext is created for every message you parse/encode. 073 * </li> 074 * </ul> 075 */ 076public class FhirContext { 077 078 private static final List<Class<? extends IBaseResource>> EMPTY_LIST = Collections.emptyList(); 079 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContext.class); 080 private volatile Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition = Collections.emptyMap(); 081 private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap(); 082 private HapiLocalizer myLocalizer = new HapiLocalizer(); 083 private volatile Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinition = Collections.emptyMap(); 084 private volatile Map<String, RuntimeResourceDefinition> myNameToResourceDefinition = Collections.emptyMap(); 085 private volatile Map<String, Class<? extends IBaseResource>> myNameToResourceType; 086 private volatile INarrativeGenerator myNarrativeGenerator; 087 private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler(); 088 private volatile IRestfulClientFactory myRestfulClientFactory; 089 private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; 090 private final IFhirVersion myVersion; 091 private Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> myVersionToNameToResourceType = Collections.emptyMap(); 092 093 /** 094 * Default constructor. In most cases this is the right constructor to use. 095 */ 096 public FhirContext() { 097 this(EMPTY_LIST); 098 } 099 100 public FhirContext(Class<? extends IBaseResource> theResourceType) { 101 this(toCollection(theResourceType)); 102 } 103 104 public FhirContext(Class<?>... theResourceTypes) { 105 this(toCollection(theResourceTypes)); 106 } 107 108 public FhirContext(Collection<Class<? extends IBaseResource>> theResourceTypes) { 109 this(null, theResourceTypes); 110 } 111 112 public FhirContext(FhirVersionEnum theVersion) { 113 this(theVersion, null); 114 } 115 116 private FhirContext(FhirVersionEnum theVersion, Collection<Class<? extends IBaseResource>> theResourceTypes) { 117 VersionUtil.getVersion(); 118 119 if (theVersion != null) { 120 if (!theVersion.isPresentOnClasspath()) { 121 throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructuresForSpecifiedVersion", theVersion.name())); 122 } 123 myVersion = theVersion.getVersionImplementation(); 124 } else if (FhirVersionEnum.DSTU1.isPresentOnClasspath()) { 125 myVersion = FhirVersionEnum.DSTU1.getVersionImplementation(); 126 } else if (FhirVersionEnum.DSTU2.isPresentOnClasspath()) { 127 myVersion = FhirVersionEnum.DSTU2.getVersionImplementation(); 128 } else if (FhirVersionEnum.DSTU2_HL7ORG.isPresentOnClasspath()) { 129 myVersion = FhirVersionEnum.DSTU2_HL7ORG.getVersionImplementation(); 130 } else { 131 throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructures")); 132 } 133 134 if (theVersion == null) { 135 ourLog.info("Creating new FhirContext with auto-detected version [{}]. It is recommended to explicitly select a version for future compatibility by invoking FhirContext.forDstuX()", myVersion.getVersion().name()); 136 } else { 137 ourLog.info("Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name()); 138 } 139 140 scanResourceTypes(toElementList(theResourceTypes)); 141 } 142 143 private String createUnknownResourceNameError(String theResourceName, FhirVersionEnum theVersion) { 144 return getLocalizer().getMessage(FhirContext.class, "unknownResourceName", theResourceName, theVersion); 145 } 146 147 /** 148 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed 149 * for extending the core library. 150 */ 151 @SuppressWarnings("unchecked") 152 public BaseRuntimeElementDefinition<?> getElementDefinition(Class<? extends IBase> theElementType) { 153 BaseRuntimeElementDefinition<?> retVal = myClassToElementDefinition.get(theElementType); 154 if (retVal == null) { 155 retVal = scanDatatype((Class<? extends IElement>) theElementType); 156 } 157 return retVal; 158 } 159 160 /** 161 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed 162 * for extending the core library. 163 */ 164 public BaseRuntimeElementDefinition<?> getElementDefinition(String theElementName) { 165 return myNameToElementDefinition.get(theElementName); 166 } 167 168 /** For unit tests only */ 169 int getElementDefinitionCount() { 170 return myClassToElementDefinition.size(); 171 } 172 173 /** 174 * Returns all element definitions (resources, datatypes, etc.) 175 */ 176 public Collection<BaseRuntimeElementDefinition<?>> getElementDefinitions() { 177 return Collections.unmodifiableCollection(myClassToElementDefinition.values()); 178 } 179 180 /** 181 * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with 182 * caution 183 */ 184 public HapiLocalizer getLocalizer() { 185 if (myLocalizer == null) { 186 myLocalizer = new HapiLocalizer(); 187 } 188 return myLocalizer; 189 } 190 191 public INarrativeGenerator getNarrativeGenerator() { 192 return myNarrativeGenerator; 193 } 194 195 /** 196 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed 197 * for extending the core library. 198 */ 199 @SuppressWarnings("unchecked") 200 public RuntimeResourceDefinition getResourceDefinition(Class<? extends IBaseResource> theResourceType) { 201 if (theResourceType == null) { 202 throw new NullPointerException("theResourceType can not be null"); 203 } 204 if (Modifier.isAbstract(theResourceType.getModifiers())) { 205 throw new IllegalArgumentException("Can not scan abstract or interface class (resource definitions must be concrete classes): " + theResourceType.getName()); 206 } 207 208 RuntimeResourceDefinition retVal = (RuntimeResourceDefinition) myClassToElementDefinition.get(theResourceType); 209 if (retVal == null) { 210 retVal = scanResourceType((Class<? extends IResource>) theResourceType); 211 } 212 return retVal; 213 } 214 215 public RuntimeResourceDefinition getResourceDefinition(FhirVersionEnum theVersion, String theResourceName) { 216 Validate.notNull(theVersion, "theVersion can not be null"); 217 218 if (theVersion.equals(myVersion.getVersion())) { 219 return getResourceDefinition(theResourceName); 220 } 221 222 Map<String, Class<? extends IBaseResource>> nameToType = myVersionToNameToResourceType.get(theVersion); 223 if (nameToType == null) { 224 nameToType = new HashMap<String, Class<? extends IBaseResource>>(); 225 ModelScanner.scanVersionPropertyFile(null, nameToType, theVersion); 226 227 Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> newVersionToNameToResourceType = new HashMap<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>>(); 228 newVersionToNameToResourceType.putAll(myVersionToNameToResourceType); 229 newVersionToNameToResourceType.put(theVersion, nameToType); 230 myVersionToNameToResourceType = newVersionToNameToResourceType; 231 } 232 233 Class<? extends IBaseResource> resourceType = nameToType.get(theResourceName.toLowerCase()); 234 if (resourceType == null) { 235 throw new DataFormatException(createUnknownResourceNameError(theResourceName, theVersion)); 236 } 237 238 return getResourceDefinition(resourceType); 239 } 240 241 /** 242 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed 243 * for extending the core library. 244 */ 245 public RuntimeResourceDefinition getResourceDefinition(IBaseResource theResource) { 246 return getResourceDefinition(theResource.getClass()); 247 } 248 249 /** 250 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed 251 * for extending the core library. 252 */ 253 @SuppressWarnings("unchecked") 254 public RuntimeResourceDefinition getResourceDefinition(String theResourceName) { 255 Validate.notBlank(theResourceName, "theResourceName must not be blank"); 256 257 String resourceName = theResourceName; 258 259 /* 260 * TODO: this is a bit of a hack, really we should have a translation table based on a property file or 261 * something so that we can detect names like diagnosticreport 262 */ 263 if (Character.isLowerCase(resourceName.charAt(0))) { 264 resourceName = WordUtils.capitalize(resourceName); 265 } 266 267 Validate.notBlank(resourceName, "Resource name must not be blank"); 268 269 RuntimeResourceDefinition retVal = myNameToResourceDefinition.get(resourceName); 270 271 if (retVal == null) { 272 Class<? extends IBaseResource> clazz = myNameToResourceType.get(resourceName.toLowerCase()); 273 if (clazz == null) { 274 throw new DataFormatException(createUnknownResourceNameError(resourceName, myVersion.getVersion())); 275 } 276 if (IBaseResource.class.isAssignableFrom(clazz)) { 277 retVal = scanResourceType((Class<? extends IResource>) clazz); 278 } 279 } 280 281 return retVal; 282 } 283 284 /** 285 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed 286 * for extending the core library. 287 */ 288 public RuntimeResourceDefinition getResourceDefinitionById(String theId) { 289 return myIdToResourceDefinition.get(theId); 290 } 291 292 /** 293 * Returns the scanned runtime models. This is an advanced feature which is generally only needed for extending the 294 * core library. 295 */ 296 public Collection<RuntimeResourceDefinition> getResourceDefinitions() { 297 return myIdToResourceDefinition.values(); 298 } 299 300 public IRestfulClientFactory getRestfulClientFactory() { 301 if (myRestfulClientFactory == null) { 302 myRestfulClientFactory = new RestfulClientFactory(this); 303 } 304 return myRestfulClientFactory; 305 } 306 307 public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() { 308 return myRuntimeChildUndeclaredExtensionDefinition; 309 } 310 311 public IFhirVersion getVersion() { 312 return myVersion; 313 } 314 315 /** 316 * This method should be considered experimental and will likely change in future releases 317 * of HAPI. Use with caution! 318 */ 319 public IVersionSpecificBundleFactory newBundleFactory() { 320 return myVersion.newBundleFactory(this); 321 } 322 323 /** 324 * Create and return a new JSON parser. 325 * 326 * <p> 327 * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread 328 * or every message being parsed/encoded. 329 * </p> 330 * <p> 331 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed 332 * without incurring any performance penalty 333 * </p> 334 */ 335 public IParser newJsonParser() { 336 return new JsonParser(this, myParserErrorHandler); 337 } 338 339 /** 340 * Instantiates a new client instance. This method requires an interface which is defined specifically for your use 341 * cases to contain methods for each of the RESTful operations you wish to implement (e.g. "read ImagingStudy", 342 * "search Patient by identifier", etc.). This interface must extend {@link IRestfulClient} (or commonly its 343 * sub-interface {@link IBasicClient}). See the <a 344 * href="http://jamesagnew.github.io/hapi-fhir/doc_rest_client.html">RESTful Client</a> documentation for more 345 * information on how to define this interface. 346 * 347 * <p> 348 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every operation invocation 349 * without incurring any performance penalty 350 * </p> 351 * 352 * @param theClientType 353 * The client type, which is an interface type to be instantiated 354 * @param theServerBase 355 * The URL of the base for the restful FHIR server to connect to 356 * @return A newly created client 357 * @throws ConfigurationException 358 * If the interface type is not an interface 359 */ 360 public <T extends IRestfulClient> T newRestfulClient(Class<T> theClientType, String theServerBase) { 361 return getRestfulClientFactory().newClient(theClientType, theServerBase); 362 } 363 364 /** 365 * Instantiates a new generic client. A generic client is able to perform any of the FHIR RESTful operations against 366 * a compliant server, but does not have methods defining the specific functionality required (as is the case with 367 * {@link #newRestfulClient(Class, String) non-generic clients}). 368 * 369 * <p> 370 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every operation invocation 371 * without incurring any performance penalty 372 * </p> 373 * 374 * @param theServerBase 375 * The URL of the base for the restful FHIR server to connect to 376 */ 377 public IGenericClient newRestfulGenericClient(String theServerBase) { 378 return getRestfulClientFactory().newGenericClient(theServerBase); 379 } 380 381 public FhirTerser newTerser() { 382 return new FhirTerser(this); 383 } 384 385 /** 386 * Create a new validator instance. 387 * <p> 388 * Note on thread safety: Validators are thread safe, you may use a single validator 389 * in multiple threads. (This is in contrast to parsers) 390 * </p> 391 */ 392 public FhirValidator newValidator() { 393 return new FhirValidator(this); 394 } 395 396 public ViewGenerator newViewGenerator() { 397 return new ViewGenerator(this); 398 } 399 400 /** 401 * Create and return a new XML parser. 402 * 403 * <p> 404 * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread 405 * or every message being parsed/encoded. 406 * </p> 407 * <p> 408 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed 409 * without incurring any performance penalty 410 * </p> 411 */ 412 public IParser newXmlParser() { 413 return new XmlParser(this, myParserErrorHandler); 414 } 415 416 private BaseRuntimeElementDefinition<?> scanDatatype(Class<? extends IElement> theResourceType) { 417 ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<Class<? extends IElement>>(); 418 resourceTypes.add(theResourceType); 419 Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> defs = scanResourceTypes(resourceTypes); 420 return defs.get(theResourceType); 421 } 422 423 private RuntimeResourceDefinition scanResourceType(Class<? extends IResource> theResourceType) { 424 ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<Class<? extends IElement>>(); 425 resourceTypes.add(theResourceType); 426 Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> defs = scanResourceTypes(resourceTypes); 427 return (RuntimeResourceDefinition) defs.get(theResourceType); 428 } 429 430 private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> scanResourceTypes(Collection<Class<? extends IElement>> theResourceTypes) { 431 ModelScanner scanner = new ModelScanner(this, myVersion.getVersion(), myClassToElementDefinition, theResourceTypes); 432 if (myRuntimeChildUndeclaredExtensionDefinition == null) { 433 myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition(); 434 } 435 436 Map<String, BaseRuntimeElementDefinition<?>> nameToElementDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>(); 437 nameToElementDefinition.putAll(myNameToElementDefinition); 438 nameToElementDefinition.putAll(scanner.getNameToElementDefinitions()); 439 440 Map<String, RuntimeResourceDefinition> nameToResourceDefinition = new HashMap<String, RuntimeResourceDefinition>(); 441 nameToResourceDefinition.putAll(myNameToResourceDefinition); 442 nameToResourceDefinition.putAll(scanner.getNameToResourceDefinition()); 443 444 Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> classToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>(); 445 classToElementDefinition.putAll(myClassToElementDefinition); 446 classToElementDefinition.putAll(scanner.getClassToElementDefinitions()); 447 448 Map<String, RuntimeResourceDefinition> idToElementDefinition = new HashMap<String, RuntimeResourceDefinition>(); 449 idToElementDefinition.putAll(myIdToResourceDefinition); 450 idToElementDefinition.putAll(scanner.getIdToResourceDefinition()); 451 452 myNameToElementDefinition = nameToElementDefinition; 453 myClassToElementDefinition = classToElementDefinition; 454 myIdToResourceDefinition = idToElementDefinition; 455 myNameToResourceDefinition = nameToResourceDefinition; 456 457 myNameToResourceType = scanner.getNameToResourceType(); 458 459 return classToElementDefinition; 460 } 461 462 /** 463 * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with 464 * caution 465 */ 466 public void setLocalizer(HapiLocalizer theMessages) { 467 myLocalizer = theMessages; 468 } 469 470 public void setNarrativeGenerator(INarrativeGenerator theNarrativeGenerator) { 471 myNarrativeGenerator = theNarrativeGenerator; 472 } 473 474 /** 475 * Sets a parser error handler to use by default on all parsers 476 * 477 * @param theParserErrorHandler The error handler 478 */ 479 public void setParserErrorHandler(IParserErrorHandler theParserErrorHandler) { 480 Validate.notNull(theParserErrorHandler, "theParserErrorHandler must not be null"); 481 myParserErrorHandler = theParserErrorHandler; 482 } 483 484 @SuppressWarnings({ "cast" }) 485 private List<Class<? extends IElement>> toElementList(Collection<Class<? extends IBaseResource>> theResourceTypes) { 486 if (theResourceTypes == null) { 487 return null; 488 } 489 List<Class<? extends IElement>> resTypes = new ArrayList<Class<? extends IElement>>(); 490 for (Class<? extends IBaseResource> next : theResourceTypes) { 491 resTypes.add((Class<? extends IElement>) next); 492 } 493 return resTypes; 494 } 495 496 /** 497 * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU1} 498 */ 499 public static FhirContext forDstu1() { 500 return new FhirContext(FhirVersionEnum.DSTU1); 501 } 502 503 /** 504 * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU 2} 505 */ 506 public static FhirContext forDstu2() { 507 return new FhirContext(FhirVersionEnum.DSTU2); 508 } 509 510 /** 511 * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU3 DSTU 3} 512 * 513 * @since 1.4 514 */ 515 public static FhirContext forDstu3() { 516 return new FhirContext(FhirVersionEnum.DSTU3); 517 } 518 519 public static FhirContext forDstu2Hl7Org() { 520 return new FhirContext(FhirVersionEnum.DSTU2_HL7ORG); 521 } 522 523 private static Collection<Class<? extends IBaseResource>> toCollection(Class<? extends IBaseResource> theResourceType) { 524 ArrayList<Class<? extends IBaseResource>> retVal = new ArrayList<Class<? extends IBaseResource>>(1); 525 retVal.add(theResourceType); 526 return retVal; 527 } 528 529 @SuppressWarnings("unchecked") 530 private static List<Class<? extends IBaseResource>> toCollection(Class<?>[] theResourceTypes) { 531 ArrayList<Class<? extends IBaseResource>> retVal = new ArrayList<Class<? extends IBaseResource>>(1); 532 for (Class<?> clazz : theResourceTypes) { 533 if (!IResource.class.isAssignableFrom(clazz)) { 534 throw new IllegalArgumentException(clazz.getCanonicalName() + " is not an instance of " + IResource.class.getSimpleName()); 535 } 536 retVal.add((Class<? extends IResource>) clazz); 537 } 538 return retVal; 539 } 540 541}