All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.uhn.fhir.context.FhirContext Maven / Gradle / Ivy

Go to download

Builds the hapi fhir base. Requires Common lang, commons-text, and guava be built first.

There is a newer version: 7.0.2-r12
Show newest version
/*
 * #%L
 * HAPI FHIR - Core Library
 * %%
 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package ca.uhn.fhir.context;

import ca.uhn.fhir.context.api.AddProfileTagEnum;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.view.ViewGenerator;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.IParserErrorHandler;
import ca.uhn.fhir.parser.JsonParser;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.parser.NDJsonParser;
import ca.uhn.fhir.parser.RDFParser;
import ca.uhn.fhir.parser.XmlParser;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.client.api.IRestfulClientFactory;
import ca.uhn.fhir.system.HapiSystemProperties;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.VersionUtil;
import ca.uhn.fhir.validation.FhirValidator;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.jena.riot.Lang;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * The FHIR context is the central starting point for the use of the HAPI FHIR API. It should be created once, and then
 * used as a factory for various other types of objects (parsers, clients, etc.).
 *
 * 

* Important usage notes: *

*
    *
  • * Thread safety: This class is thread safe and may be shared between multiple processing * threads, except for the {@link #registerCustomType} and {@link #registerCustomTypes} methods. *
  • *
  • * Performance: This class is expensive to create, as it scans every resource class it needs to parse or encode * to build up an internal model of those classes. For that reason, you should try to create one FhirContext instance * which remains for the life of your application and reuse that instance. Note that it will not cause problems to * create multiple instances (ie. resources originating from one FhirContext may be passed to parsers originating from * another) but you will incur a performance penalty if a new FhirContext is created for every message you parse/encode. *
  • *
*/ public class FhirContext { private static final List> EMPTY_LIST = Collections.emptyList(); private static final Map ourStaticContexts = Collections.synchronizedMap(new EnumMap<>(FhirVersionEnum.class)); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContext.class); private final IFhirVersion myVersion; private final Map> myDefaultTypeForProfile = new HashMap<>(); private final Set myPerformanceOptions = new HashSet<>(); private final Collection> myResourceTypesToScan; private AddProfileTagEnum myAddProfileTagWhenEncoding = AddProfileTagEnum.ONLY_FOR_CUSTOM; private volatile Map, BaseRuntimeElementDefinition> myClassToElementDefinition = Collections.emptyMap(); private ArrayList> myCustomTypes; private final Set myCustomResourceNames = new HashSet<>(); private volatile Map myIdToResourceDefinition = Collections.emptyMap(); private volatile boolean myInitialized; private volatile boolean myInitializing = false; private HapiLocalizer myLocalizer = new HapiLocalizer(); private volatile Map> myNameToElementDefinition = Collections.emptyMap(); private volatile Map myNameToResourceDefinition = Collections.emptyMap(); private volatile Map> myNameToResourceType; private volatile INarrativeGenerator myNarrativeGenerator; private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler(); private ParserOptions myParserOptions = new ParserOptions(); private volatile IRestfulClientFactory myRestfulClientFactory; private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; private IValidationSupport myValidationSupport; private Map>> myVersionToNameToResourceType = Collections.emptyMap(); private volatile Set myResourceNames; private volatile Boolean myFormatXmlSupported; private volatile Boolean myFormatJsonSupported; private volatile Boolean myFormatNDJsonSupported; private volatile Boolean myFormatRdfSupported; private IFhirValidatorFactory myFhirValidatorFactory = FhirValidator::new; /** * @deprecated It is recommended that you use one of the static initializer methods instead * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} */ @Deprecated public FhirContext() { this(EMPTY_LIST); } /** * @deprecated It is recommended that you use one of the static initializer methods instead * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} */ @Deprecated public FhirContext(final Class theResourceType) { this(toCollection(theResourceType)); } /** * @deprecated It is recommended that you use one of the static initializer methods instead * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} */ @Deprecated public FhirContext(final Class... theResourceTypes) { this(toCollection(theResourceTypes)); } /** * @deprecated It is recommended that you use one of the static initializer methods instead * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()} */ @Deprecated public FhirContext(final Collection> theResourceTypes) { this(null, theResourceTypes); } /** * In most cases it is recommended that you use one of the static initializer methods instead * of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()} or {@link #forR4()}, but * this method can also be used if you wish to supply the version programmatically. */ public FhirContext(final FhirVersionEnum theVersion) { this(theVersion, null); } private FhirContext( final FhirVersionEnum theVersion, final Collection> theResourceTypes) { VersionUtil.getVersion(); if (theVersion != null) { if (!theVersion.isPresentOnClasspath()) { throw new IllegalStateException(Msg.code(1680) + getLocalizer() .getMessage(FhirContext.class, "noStructuresForSpecifiedVersion", theVersion.name())); } myVersion = theVersion.getVersionImplementation(); } else if (FhirVersionEnum.DSTU2.isPresentOnClasspath()) { myVersion = FhirVersionEnum.DSTU2.getVersionImplementation(); } else if (FhirVersionEnum.DSTU2_HL7ORG.isPresentOnClasspath()) { myVersion = FhirVersionEnum.DSTU2_HL7ORG.getVersionImplementation(); } else if (FhirVersionEnum.DSTU2_1.isPresentOnClasspath()) { myVersion = FhirVersionEnum.DSTU2_1.getVersionImplementation(); } else if (FhirVersionEnum.DSTU3.isPresentOnClasspath()) { myVersion = FhirVersionEnum.DSTU3.getVersionImplementation(); } else if (FhirVersionEnum.R4.isPresentOnClasspath()) { myVersion = FhirVersionEnum.R4.getVersionImplementation(); } else if (FhirVersionEnum.R4B.isPresentOnClasspath()) { myVersion = FhirVersionEnum.R4B.getVersionImplementation(); } else { throw new IllegalStateException( Msg.code(1681) + getLocalizer().getMessage(FhirContext.class, "noStructures")); } if (theVersion == null) { 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()); } else { if (HapiSystemProperties.isUnitTestModeEnabled()) { String calledAt = ExceptionUtils.getStackFrames(new Throwable())[4]; ourLog.info( "Creating new FHIR context for FHIR version [{}]{}", myVersion.getVersion().name(), calledAt); } else { ourLog.info( "Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name()); } } myResourceTypesToScan = theResourceTypes; /* * Check if we're running in Android mode and configure the context appropriately if so */ try { Class clazz = Class.forName("ca.uhn.fhir.android.AndroidMarker"); ourLog.info("Android mode detected, configuring FhirContext for Android operation"); try { Method method = clazz.getMethod("configureContext", FhirContext.class); method.invoke(null, this); } catch (Throwable e) { ourLog.warn("Failed to configure context for Android operation", e); } } catch (ClassNotFoundException e) { ourLog.trace("Android mode not detected"); } } /** * @since 5.6.0 */ public static FhirContext forDstu2Cached() { return forCached(FhirVersionEnum.DSTU2); } /** * @since 6.2.0 */ public static FhirContext forDstu2Hl7OrgCached() { return forCached(FhirVersionEnum.DSTU2_HL7ORG); } /** * @since 5.5.0 */ public static FhirContext forDstu3Cached() { return forCached(FhirVersionEnum.DSTU3); } /** * @since 5.5.0 */ public static FhirContext forR4Cached() { return forCached(FhirVersionEnum.R4); } /** * @since 6.1.0 */ public static FhirContext forR4BCached() { return forCached(FhirVersionEnum.R4B); } /** * @since 5.5.0 */ public static FhirContext forR5Cached() { return forCached(FhirVersionEnum.R5); } private String createUnknownResourceNameError(final String theResourceName, final FhirVersionEnum theVersion) { return getLocalizer().getMessage(FhirContext.class, "unknownResourceName", theResourceName, theVersion); } private void ensureCustomTypeList() { myClassToElementDefinition.clear(); if (myCustomTypes == null) { myCustomTypes = new ArrayList<>(); } } /** * When encoding resources, this setting configures the parser to include * an entry in the resource's metadata section which indicates which profile(s) the * resource claims to conform to. The default is {@link AddProfileTagEnum#ONLY_FOR_CUSTOM}. * * @see #setAddProfileTagWhenEncoding(AddProfileTagEnum) for more information */ public AddProfileTagEnum getAddProfileTagWhenEncoding() { return myAddProfileTagWhenEncoding; } /** * When encoding resources, this setting configures the parser to include * an entry in the resource's metadata section which indicates which profile(s) the * resource claims to conform to. The default is {@link AddProfileTagEnum#ONLY_FOR_CUSTOM}. *

* This feature is intended for situations where custom resource types are being used, * avoiding the need to manually add profile declarations for these custom types. *

*

* See Profiling and Extensions * for more information on using custom types. *

*

* Note that this feature automatically adds the profile, but leaves any profile tags * which have been manually added in place as well. *

* * @param theAddProfileTagWhenEncoding The add profile mode (must not be null) */ public void setAddProfileTagWhenEncoding(final AddProfileTagEnum theAddProfileTagWhenEncoding) { Validate.notNull(theAddProfileTagWhenEncoding, "theAddProfileTagWhenEncoding must not be null"); myAddProfileTagWhenEncoding = theAddProfileTagWhenEncoding; } Collection getAllResourceDefinitions() { validateInitialized(); return myNameToResourceDefinition.values(); } /** * Returns the default resource type for the given profile * * @see #setDefaultTypeForProfile(String, Class) */ public Class getDefaultTypeForProfile(final String theProfile) { validateInitialized(); return myDefaultTypeForProfile.get(theProfile); } /** * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed * for extending the core library. */ @SuppressWarnings("unchecked") public BaseRuntimeElementDefinition getElementDefinition(final Class theElementType) { validateInitialized(); BaseRuntimeElementDefinition retVal = myClassToElementDefinition.get(theElementType); if (retVal == null) { retVal = scanDatatype((Class) theElementType); } return retVal; } /** * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed * for extending the core library. *

* Note that this method is case insensitive! *

*/ @Nullable public BaseRuntimeElementDefinition getElementDefinition(final String theElementName) { validateInitialized(); return myNameToElementDefinition.get(theElementName.toLowerCase()); } /** * Returns all element definitions (resources, datatypes, etc.) */ public Collection> getElementDefinitions() { validateInitialized(); return Collections.unmodifiableCollection(myClassToElementDefinition.values()); } /** * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with * caution */ public HapiLocalizer getLocalizer() { if (myLocalizer == null) { myLocalizer = new HapiLocalizer(); } return myLocalizer; } /** * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with * caution */ public void setLocalizer(final HapiLocalizer theMessages) { myLocalizer = theMessages; } public INarrativeGenerator getNarrativeGenerator() { return myNarrativeGenerator; } public FhirContext setNarrativeGenerator(final INarrativeGenerator theNarrativeGenerator) { myNarrativeGenerator = theNarrativeGenerator; return this; } /** * Returns the parser options object which will be used to supply default * options to newly created parsers * * @return The parser options - Will not return null */ public ParserOptions getParserOptions() { return myParserOptions; } /** * Sets the parser options object which will be used to supply default * options to newly created parsers * * @param theParserOptions The parser options object - Must not be null */ public void setParserOptions(final ParserOptions theParserOptions) { Validate.notNull(theParserOptions, "theParserOptions must not be null"); myParserOptions = theParserOptions; } /** * Get the configured performance options */ public Set getPerformanceOptions() { return myPerformanceOptions; } // /** // * Return an unmodifiable collection containing all known resource definitions // */ // public Collection getResourceDefinitions() { // // Set> datatypes = Collections.emptySet(); // Map, BaseRuntimeElementDefinition> existing = Collections.emptyMap(); // HashMap> types = new HashMap>(); // ModelScanner.scanVersionPropertyFile(datatypes, types, myVersion.getVersion(), existing); // for (int next : types.) // // return Collections.unmodifiableCollection(myIdToResourceDefinition.values()); // } /** * Sets the configured performance options * * @see PerformanceOptionsEnum for a list of available options */ public void setPerformanceOptions(final Collection theOptions) { myPerformanceOptions.clear(); if (theOptions != null) { myPerformanceOptions.addAll(theOptions); } } /** * Sets the configured performance options * * @see PerformanceOptionsEnum for a list of available options */ public void setPerformanceOptions(final PerformanceOptionsEnum... thePerformanceOptions) { Collection asList = null; if (thePerformanceOptions != null) { asList = Arrays.asList(thePerformanceOptions); } setPerformanceOptions(asList); } /** * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed * for extending the core library. */ public RuntimeResourceDefinition getResourceDefinition(final Class theResourceType) { validateInitialized(); Validate.notNull(theResourceType, "theResourceType can not be null"); if (Modifier.isAbstract(theResourceType.getModifiers())) { throw new IllegalArgumentException(Msg.code(1682) + "Can not scan abstract or interface class (resource definitions must be concrete classes): " + theResourceType.getName()); } RuntimeResourceDefinition retVal = (RuntimeResourceDefinition) myClassToElementDefinition.get(theResourceType); if (retVal == null) { retVal = scanResourceType(theResourceType); } return retVal; } public RuntimeResourceDefinition getResourceDefinition( final FhirVersionEnum theVersion, final String theResourceName) { Validate.notNull(theVersion, "theVersion can not be null"); validateInitialized(); if (theVersion.equals(myVersion.getVersion())) { return getResourceDefinition(theResourceName); } Map> nameToType = myVersionToNameToResourceType.get(theVersion); if (nameToType == null) { nameToType = new HashMap<>(); Map, BaseRuntimeElementDefinition> existing = new HashMap<>(); ModelScanner.scanVersionPropertyFile(null, nameToType, theVersion, existing); Map>> newVersionToNameToResourceType = new HashMap<>(); newVersionToNameToResourceType.putAll(myVersionToNameToResourceType); newVersionToNameToResourceType.put(theVersion, nameToType); myVersionToNameToResourceType = newVersionToNameToResourceType; } Class resourceType = nameToType.get(theResourceName.toLowerCase()); if (resourceType == null) { throw new DataFormatException(Msg.code(1683) + createUnknownResourceNameError(theResourceName, theVersion)); } return getResourceDefinition(resourceType); } /** * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed * for extending the core library. */ public RuntimeResourceDefinition getResourceDefinition(final IBaseResource theResource) { validateInitialized(); Validate.notNull(theResource, "theResource must not be null"); return getResourceDefinition(theResource.getClass()); } /** * Returns the name of a given resource class. */ public String getResourceType(final Class theResourceType) { return getResourceDefinition(theResourceType).getName(); } /** * Returns the name of the scanned runtime model for the given type. This is an advanced feature which is generally only needed * for extending the core library. */ public String getResourceType(final IBaseResource theResource) { return getResourceDefinition(theResource).getName(); } /* * Returns the type of the scanned runtime model for the given type. This is an advanced feature which is generally only needed * for extending the core library. *

* Note that this method is case insensitive! *

* * @throws DataFormatException If the resource name is not known */ public String getResourceType(final String theResourceName) throws DataFormatException { return getResourceDefinition(theResourceName).getName(); } /* * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed * for extending the core library. *

* Note that this method is case insensitive! *

* * @throws DataFormatException If the resource name is not known */ public RuntimeResourceDefinition getResourceDefinition(final String theResourceName) throws DataFormatException { validateInitialized(); Validate.notBlank(theResourceName, "theResourceName must not be blank"); String resourceName = theResourceName.toLowerCase(); RuntimeResourceDefinition retVal = myNameToResourceDefinition.get(resourceName); if (retVal == null) { Class clazz = myNameToResourceType.get(resourceName.toLowerCase()); if (clazz == null) { // *********************************************************************** // Multiple spots in HAPI FHIR and Smile CDR depend on DataFormatException // being thrown by this method, don't change that. // *********************************************************************** throw new DataFormatException( Msg.code(1684) + createUnknownResourceNameError(theResourceName, myVersion.getVersion())); } if (IBaseResource.class.isAssignableFrom(clazz)) { retVal = scanResourceType(clazz); } } return retVal; } /** * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed * for extending the core library. */ public RuntimeResourceDefinition getResourceDefinitionById(final String theId) { validateInitialized(); return myIdToResourceDefinition.get(theId); } /** * Returns the scanned runtime models. This is an advanced feature which is generally only needed for extending the * core library. */ public Collection getResourceDefinitionsWithExplicitId() { validateInitialized(); return myIdToResourceDefinition.values(); } /** * Returns an unmodifiable set containing all resource names known to this * context * * @since 5.1.0 */ public Set getResourceTypes() { Set resourceNames = myResourceNames; if (resourceNames == null) { resourceNames = buildResourceNames(); myResourceNames = resourceNames; } return resourceNames; } @Nonnull private Set buildResourceNames() { Set retVal = new HashSet<>(); Properties props = new Properties(); try (InputStream propFile = myVersion.getFhirVersionPropertiesFile()) { props.load(propFile); } catch (IOException e) { throw new ConfigurationException(Msg.code(1685) + "Failed to load version properties file", e); } Enumeration propNames = props.propertyNames(); while (propNames.hasMoreElements()) { String next = (String) propNames.nextElement(); if (next.startsWith("resource.")) { retVal.add(next.substring("resource.".length()).trim()); } } retVal.addAll(myCustomResourceNames); return retVal; } /** * Get the restful client factory. If no factory has been set, this will be initialized with * a new ApacheRestfulClientFactory. * * @return the factory used to create the restful clients */ public IRestfulClientFactory getRestfulClientFactory() { if (myRestfulClientFactory == null) { try { myRestfulClientFactory = (IRestfulClientFactory) ReflectionUtil.newInstance( Class.forName("ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory"), FhirContext.class, this); } catch (ClassNotFoundException e) { throw new ConfigurationException( Msg.code(1686) + "hapi-fhir-client does not appear to be on the classpath"); } } return myRestfulClientFactory; } /** * Set the restful client factory * * @param theRestfulClientFactory The new client factory (must not be null) */ public void setRestfulClientFactory(final IRestfulClientFactory theRestfulClientFactory) { Validate.notNull(theRestfulClientFactory, "theRestfulClientFactory must not be null"); this.myRestfulClientFactory = theRestfulClientFactory; } public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() { validateInitialized(); return myRuntimeChildUndeclaredExtensionDefinition; } /** * Returns the validation support module configured for this context, creating a default * implementation if no module has been passed in via the {@link #setValidationSupport(IValidationSupport)} * method * * @see #setValidationSupport(IValidationSupport) */ public IValidationSupport getValidationSupport() { IValidationSupport retVal = myValidationSupport; if (retVal == null) { retVal = new DefaultProfileValidationSupport(this); /* * If hapi-fhir-validation is on the classpath, we can create a much more robust * validation chain using the classes found in that package */ String inMemoryTermSvcType = "org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport"; String commonCodeSystemsSupportType = "org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService"; if (ReflectionUtil.typeExists(inMemoryTermSvcType)) { IValidationSupport inMemoryTermSvc = ReflectionUtil.newInstanceOrReturnNull( inMemoryTermSvcType, IValidationSupport.class, new Class[] {FhirContext.class}, new Object[] {this}); IValidationSupport commonCodeSystemsSupport = ReflectionUtil.newInstanceOrReturnNull( commonCodeSystemsSupportType, IValidationSupport.class, new Class[] {FhirContext.class}, new Object[] {this}); retVal = ReflectionUtil.newInstanceOrReturnNull( "org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain", IValidationSupport.class, new Class[] {IValidationSupport[].class}, new Object[] {new IValidationSupport[] {retVal, inMemoryTermSvc, commonCodeSystemsSupport}}); assert retVal != null : "Failed to instantiate " + "org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain"; } myValidationSupport = retVal; } return retVal; } /** * Sets the validation support module to use for this context. The validation support module * is used to supply underlying infrastructure such as conformance resources (StructureDefinition, ValueSet, etc) * as well as to provide terminology services to modules such as the validator and FluentPath executor */ public void setValidationSupport(IValidationSupport theValidationSupport) { myValidationSupport = theValidationSupport; } public IFhirVersion getVersion() { return myVersion; } /** * Returns true if any default types for specific profiles have been defined * within this context. * * @see #setDefaultTypeForProfile(String, Class) * @see #getDefaultTypeForProfile(String) */ public boolean hasDefaultTypeForProfile() { validateInitialized(); return !myDefaultTypeForProfile.isEmpty(); } /** * @return Returns true if the XML serialization format is supported, based on the * available libraries on the classpath. * * @since 5.4.0 */ public boolean isFormatXmlSupported() { Boolean retVal = myFormatXmlSupported; if (retVal == null) { retVal = tryToInitParser(() -> newXmlParser()); myFormatXmlSupported = retVal; } return retVal; } /** * @return Returns true if the JSON serialization format is supported, based on the * available libraries on the classpath. * * @since 5.4.0 */ public boolean isFormatJsonSupported() { Boolean retVal = myFormatJsonSupported; if (retVal == null) { retVal = tryToInitParser(() -> newJsonParser()); myFormatJsonSupported = retVal; } return retVal; } /** * @return Returns true if the NDJSON serialization format is supported, based on the * available libraries on the classpath. * * @since 5.6.0 */ public boolean isFormatNDJsonSupported() { Boolean retVal = myFormatNDJsonSupported; if (retVal == null) { retVal = tryToInitParser(() -> newNDJsonParser()); myFormatNDJsonSupported = retVal; } return retVal; } /** * @return Returns true if the RDF serialization format is supported, based on the * available libraries on the classpath. * * @since 5.4.0 */ public boolean isFormatRdfSupported() { Boolean retVal = myFormatRdfSupported; if (retVal == null) { retVal = tryToInitParser(() -> newRDFParser()); myFormatRdfSupported = retVal; } return retVal; } public IVersionSpecificBundleFactory newBundleFactory() { return myVersion.newBundleFactory(this); } /** * @since 2.2 * @deprecated Deprecated in HAPI FHIR 5.0.0. Use {@link #newFhirPath()} instead. */ @Deprecated public IFhirPath newFluentPath() { return newFhirPath(); } /** * Creates a new FhirPath engine which can be used to evaluate * path expressions over FHIR resources. Note that this engine will use the * {@link IValidationSupport context validation support} module which is * configured on the context at the time this method is called. *

* In other words, you may wish to call {@link #setValidationSupport(IValidationSupport)} before * calling {@link #newFluentPath()} *

*

* Note that this feature was added for FHIR DSTU3 and is not available * for contexts configured to use an older version of FHIR. Calling this method * on a context for a previous version of fhir will result in an * {@link UnsupportedOperationException} *

* * @since 5.0.0 */ public IFhirPath newFhirPath() { return myVersion.createFhirPathExecutor(this); } /** * Create and return a new JSON parser. * *

* Thread safety: Parsers are not guaranteed to be thread safe. Create a new parser instance for every thread * or every message being parsed/encoded. *

*

* Performance Note: This method is cheap to call, and may be called once for every message being processed * without incurring any performance penalty *

*/ public IParser newJsonParser() { return new JsonParser(this, myParserErrorHandler); } /** * Create and return a new NDJSON parser. * *

* Thread safety: Parsers are not guaranteed to be thread safe. Create a new parser instance for every thread * or every message being parsed/encoded. *

*

* Performance Note: This method is cheap to call, and may be called once for every message being processed * without incurring any performance penalty *

*

* The NDJsonParser provided here is expected to translate between legal NDJson and FHIR Bundles. * In particular, it is able to encode the resources in a FHIR Bundle to NDJson, as well as decode * NDJson into a FHIR "collection"-type Bundle populated with the resources described in the NDJson. * It will throw an exception in the event where it is asked to encode to anything other than a FHIR Bundle * or where it is asked to decode into anything other than a FHIR Bundle. *

*/ public IParser newNDJsonParser() { return new NDJsonParser(this, myParserErrorHandler); } /** * Create and return a new RDF parser. * *

* Thread safety: Parsers are not guaranteed to be thread safe. Create a new parser instance for every thread * or every message being parsed/encoded. *

*

* Performance Note: This method is cheap to call, and may be called once for every message being processed * without incurring any performance penalty *

*/ public IParser newRDFParser() { return new RDFParser(this, myParserErrorHandler, Lang.TURTLE); } /** * Instantiates a new client instance. This method requires an interface which is defined specifically for your use * cases to contain methods for each of the RESTful operations you wish to implement (e.g. "read ImagingStudy", * "search Patient by identifier", etc.). This interface must extend {@link IRestfulClient} (or commonly its * sub-interface {@link IBasicClient}). See the RESTful Client documentation for more * information on how to define this interface. * *

* Performance Note: This method is cheap to call, and may be called once for every operation invocation * without incurring any performance penalty *

* * @param theClientType The client type, which is an interface type to be instantiated * @param theServerBase The URL of the base for the restful FHIR server to connect to * @return A newly created client * @throws ConfigurationException If the interface type is not an interface */ public T newRestfulClient(final Class theClientType, final String theServerBase) { return getRestfulClientFactory().newClient(theClientType, theServerBase); } /** * Instantiates a new generic client. A generic client is able to perform any of the FHIR RESTful operations against * a compliant server, but does not have methods defining the specific functionality required (as is the case with * {@link #newRestfulClient(Class, String) non-generic clients}). * *

* Performance Note: This method performs an additional GET request to /metadata before * the desired request is performed. *

* * @param theServerBase The URL of the base for the restful FHIR server to connect to */ public IGenericClient newRestfulGenericClient(final String theServerBase) { return getRestfulClientFactory().newGenericClient(theServerBase); } public FhirTerser newTerser() { return new FhirTerser(this); } /** * Create a new validator instance. *

* Note on thread safety: Validators are thread safe, you may use a single validator * in multiple threads. (This is in contrast to parsers) *

*/ public FhirValidator newValidator() { return myFhirValidatorFactory.newFhirValidator(this); } public ViewGenerator newViewGenerator() { return new ViewGenerator(this); } /** * Create and return a new XML parser. * *

* Thread safety: Parsers are not guaranteed to be thread safe. Create a new parser instance for every thread * or every message being parsed/encoded. *

*

* Performance Note: This method is cheap to call, and may be called once for every message being processed * without incurring any performance penalty *

*/ public IParser newXmlParser() { return new XmlParser(this, myParserErrorHandler); } /** * This method may be used to register a custom resource or datatype. Note that by using * custom types, you are creating a system that will not interoperate with other systems that * do not know about your custom type. There are valid reasons however for wanting to create * custom types and this method can be used to enable them. *

* THREAD SAFETY WARNING: This method is not thread safe. It should be called before any * threads are able to call any methods on this context. *

* * @param theType The custom type to add (must not be null) */ public void registerCustomType(final Class theType) { Validate.notNull(theType, "theType must not be null"); ensureCustomTypeList(); myCustomTypes.add(theType); myResourceNames = null; } /** * This method may be used to register a custom resource or datatype. Note that by using * custom types, you are creating a system that will not interoperate with other systems that * do not know about your custom type. There are valid reasons however for wanting to create * custom types and this method can be used to enable them. *

* THREAD SAFETY WARNING: This method is not thread safe. It should be called before any * threads are able to call any methods on this context. *

* * @param theTypes The custom types to add (must not be null or contain null elements in the collection) */ public void registerCustomTypes(final Collection> theTypes) { Validate.notNull(theTypes, "theTypes must not be null"); Validate.noNullElements(theTypes.toArray(), "theTypes must not contain any null elements"); ensureCustomTypeList(); myCustomTypes.addAll(theTypes); myResourceNames = null; } private BaseRuntimeElementDefinition scanDatatype(final Class theResourceType) { ArrayList> resourceTypes = new ArrayList<>(); resourceTypes.add(theResourceType); Map, BaseRuntimeElementDefinition> defs = scanResourceTypes(resourceTypes); return defs.get(theResourceType); } private RuntimeResourceDefinition scanResourceType(final Class theResourceType) { ArrayList> resourceTypes = new ArrayList<>(); resourceTypes.add(theResourceType); Map, BaseRuntimeElementDefinition> defs = scanResourceTypes(resourceTypes); return (RuntimeResourceDefinition) defs.get(theResourceType); } private synchronized Map, BaseRuntimeElementDefinition> scanResourceTypes( final Collection> theResourceTypes) { List> typesToScan = new ArrayList<>(); if (theResourceTypes != null) { typesToScan.addAll(theResourceTypes); } if (myCustomTypes != null) { typesToScan.addAll(myCustomTypes); myCustomResourceNames.addAll(myCustomTypes.stream() .map(customType -> Optional.ofNullable(customType.getAnnotation(ResourceDef.class)) .map(ResourceDef::name) .orElse(null)) // This will be caught by the call to ModelScanner .filter(Objects::nonNull) .collect(Collectors.toSet())); myCustomTypes = null; } ModelScanner scanner = new ModelScanner(this, myVersion.getVersion(), myClassToElementDefinition, typesToScan); if (myRuntimeChildUndeclaredExtensionDefinition == null) { myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition(); } Map> nameToElementDefinition = new HashMap<>(); nameToElementDefinition.putAll(myNameToElementDefinition); for (Entry> next : scanner.getNameToElementDefinitions().entrySet()) { if (!nameToElementDefinition.containsKey(next.getKey())) { nameToElementDefinition.put(next.getKey().toLowerCase(), next.getValue()); } } Map nameToResourceDefinition = new HashMap<>(); nameToResourceDefinition.putAll(myNameToResourceDefinition); for (Entry next : scanner.getNameToResourceDefinition().entrySet()) { if (!nameToResourceDefinition.containsKey(next.getKey())) { nameToResourceDefinition.put(next.getKey(), next.getValue()); } } Map, BaseRuntimeElementDefinition> classToElementDefinition = new HashMap<>(); classToElementDefinition.putAll(myClassToElementDefinition); classToElementDefinition.putAll(scanner.getClassToElementDefinitions()); for (BaseRuntimeElementDefinition next : classToElementDefinition.values()) { if (next instanceof RuntimeResourceDefinition) { if ("Bundle".equals(next.getName())) { if (!IBaseBundle.class.isAssignableFrom(next.getImplementingClass())) { throw new ConfigurationException(Msg.code(1687) + "Resource type declares resource name Bundle but does not implement IBaseBundle"); } } } } Map idToElementDefinition = new HashMap<>(); idToElementDefinition.putAll(myIdToResourceDefinition); idToElementDefinition.putAll(scanner.getIdToResourceDefinition()); myNameToElementDefinition = nameToElementDefinition; myClassToElementDefinition = classToElementDefinition; myIdToResourceDefinition = idToElementDefinition; myNameToResourceDefinition = nameToResourceDefinition; myNameToResourceType = scanner.getNameToResourceType(); myInitialized = true; return classToElementDefinition; } /** * Sets the default type which will be used when parsing a resource that is found to be * of the given profile. *

* For example, this method is invoked with the profile string of * "http://example.com/some_patient_profile" and the type of MyPatient.class, * if the parser is parsing a resource and finds that it declares that it conforms to that profile, * the MyPatient type will be used unless otherwise specified. *

* * @param theProfile The profile string, e.g. "http://example.com/some_patient_profile". Must not be * null or empty. * @param theClass The resource type, or null to clear any existing type */ public void setDefaultTypeForProfile(final String theProfile, final Class theClass) { Validate.notBlank(theProfile, "theProfile must not be null or empty"); if (theClass == null) { myDefaultTypeForProfile.remove(theProfile); } else { myDefaultTypeForProfile.put(theProfile, theClass); } } /** * Sets a parser error handler to use by default on all parsers * * @param theParserErrorHandler The error handler */ public FhirContext setParserErrorHandler(final IParserErrorHandler theParserErrorHandler) { Validate.notNull(theParserErrorHandler, "theParserErrorHandler must not be null"); myParserErrorHandler = theParserErrorHandler; return this; } /** * Set the factory method used to create FhirValidator instances * * @param theFhirValidatorFactory * @return this * @since 5.6.0 */ public FhirContext setFhirValidatorFactory(IFhirValidatorFactory theFhirValidatorFactory) { myFhirValidatorFactory = theFhirValidatorFactory; return this; } @SuppressWarnings({"cast"}) private List> toElementList( final Collection> theResourceTypes) { if (theResourceTypes == null) { return null; } List> resTypes = new ArrayList<>(); for (Class next : theResourceTypes) { resTypes.add(next); } return resTypes; } private void validateInitialized() { // See #610 if (!myInitialized) { synchronized (this) { if (!myInitialized && !myInitializing) { myInitializing = true; try { scanResourceTypes(toElementList(myResourceTypesToScan)); } catch (Exception e) { ourLog.error("Failed to initialize FhirContext", e); throw e; } finally { myInitializing = false; } } } } } @Override public String toString() { return "FhirContext[" + myVersion.getVersion().name() + "]"; } // TODO KHS add the other primitive types @Deprecated(since = "6.6.0", forRemoval = true) public IPrimitiveType getPrimitiveBoolean(Boolean theValue) { return newPrimitiveBoolean(theValue); } public IPrimitiveType newPrimitiveBoolean(Boolean theValue) { IPrimitiveType retval = (IPrimitiveType) getElementDefinition("boolean").newInstance(); retval.setValue(theValue); return retval; } public IPrimitiveType newPrimitiveString(String theValue) { IPrimitiveType retval = (IPrimitiveType) getElementDefinition("string").newInstance(); retval.setValue(theValue); return retval; } private static boolean tryToInitParser(Runnable run) { boolean retVal; try { run.run(); retVal = true; } catch (UnsupportedClassVersionError | Exception | NoClassDefFoundError e) { retVal = false; } return retVal; } /** * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU2} */ public static FhirContext forDstu2() { return new FhirContext(FhirVersionEnum.DSTU2); } /** * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2_HL7ORG DSTU2} (using the Reference * Implementation Structures) */ public static FhirContext forDstu2Hl7Org() { return new FhirContext(FhirVersionEnum.DSTU2_HL7ORG); } /** * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU2} (2016 May DSTU3 Snapshot) */ public static FhirContext forDstu2_1() { return new FhirContext(FhirVersionEnum.DSTU2_1); } /** * Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU3 DSTU3} * * @since 1.4 */ public static FhirContext forDstu3() { return new FhirContext(FhirVersionEnum.DSTU3); } /** * Creates and returns a new FhirContext with version {@link FhirVersionEnum#R4 R4} * * @since 3.0.0 */ public static FhirContext forR4() { return new FhirContext(FhirVersionEnum.R4); } /** * Creates and returns a new FhirContext with version {@link FhirVersionEnum#R4B R4B} * * @since 6.2.0 */ public static FhirContext forR4B() { return new FhirContext(FhirVersionEnum.R4B); } /** * Creates and returns a new FhirContext with version {@link FhirVersionEnum#R5 R5} * * @since 4.0.0 */ public static FhirContext forR5() { return new FhirContext(FhirVersionEnum.R5); } /** * Returns a statically cached {@literal FhirContext} instance for the given version, creating one if none exists in the * cache. One FhirContext will be kept in the cache for each FHIR version that is requested (by calling * this method for that version), and the cache will never be expired. * * @since 5.1.0 */ public static FhirContext forCached(FhirVersionEnum theFhirVersionEnum) { return ourStaticContexts.computeIfAbsent(theFhirVersionEnum, v -> new FhirContext(v)); } private static Collection> toCollection( Class theResourceType) { ArrayList> retVal = new ArrayList<>(1); retVal.add(theResourceType); return retVal; } @SuppressWarnings("unchecked") private static List> toCollection(final Class[] theResourceTypes) { ArrayList> retVal = new ArrayList>(1); for (Class clazz : theResourceTypes) { if (!IResource.class.isAssignableFrom(clazz)) { throw new IllegalArgumentException(Msg.code(1688) + clazz.getCanonicalName() + " is not an instance of " + IResource.class.getSimpleName()); } retVal.add((Class) clazz); } return retVal; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy