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