ca.uhn.fhir.context.support.DefaultProfileValidationSupportBundleStrategy Maven / Gradle / Ivy
/*-
* #%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.support;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.ClasspathUtil;
import jakarta.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
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.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
class DefaultProfileValidationSupportBundleStrategy implements IValidationSupport {
private static final String URL_PREFIX_STRUCTURE_DEFINITION = "http://hl7.org/fhir/StructureDefinition/";
private static final String URL_PREFIX_STRUCTURE_DEFINITION_BASE = "http://hl7.org/fhir/";
private static final org.slf4j.Logger ourLog =
org.slf4j.LoggerFactory.getLogger(DefaultProfileValidationSupport.class);
private final FhirContext myCtx;
private Map myCodeSystems;
private Map myStructureDefinitions;
private Map myValueSets;
private List myTerminologyResources;
private List myStructureDefinitionResources;
/**
* Constructor
*
* @param theFhirContext The context to use
*/
DefaultProfileValidationSupportBundleStrategy(FhirContext theFhirContext) {
myCtx = theFhirContext;
}
private void initializeResourceLists() {
if (myTerminologyResources != null && myStructureDefinitionResources != null) {
return;
}
List terminologyResources = new ArrayList<>();
List structureDefinitionResources = new ArrayList<>();
switch (getFhirContext().getVersion().getVersion()) {
case DSTU2:
case DSTU2_HL7ORG:
terminologyResources.add("/org/hl7/fhir/instance/model/valueset/valuesets.xml");
terminologyResources.add("/org/hl7/fhir/instance/model/valueset/v2-tables.xml");
terminologyResources.add("/org/hl7/fhir/instance/model/valueset/v3-codesystems.xml");
Properties profileNameProperties = new Properties();
try {
profileNameProperties.load(DefaultProfileValidationSupport.class.getResourceAsStream(
"/org/hl7/fhir/instance/model/profile/profiles.properties"));
for (Object nextKey : profileNameProperties.keySet()) {
structureDefinitionResources.add("/org/hl7/fhir/instance/model/profile/" + nextKey);
}
} catch (IOException e) {
throw new ConfigurationException(Msg.code(1740) + e);
}
break;
case DSTU2_1:
terminologyResources.add("/org/hl7/fhir/dstu2016may/model/valueset/valuesets.xml");
terminologyResources.add("/org/hl7/fhir/dstu2016may/model/valueset/v2-tables.xml");
terminologyResources.add("/org/hl7/fhir/dstu2016may/model/valueset/v3-codesystems.xml");
structureDefinitionResources.add("/org/hl7/fhir/dstu2016may/model/profile/profiles-resources.xml");
structureDefinitionResources.add("/org/hl7/fhir/dstu2016may/model/profile/profiles-types.xml");
structureDefinitionResources.add("/org/hl7/fhir/dstu2016may/model/profile/profiles-others.xml");
break;
case DSTU3:
terminologyResources.add("/org/hl7/fhir/dstu3/model/valueset/valuesets.xml");
terminologyResources.add("/org/hl7/fhir/dstu3/model/valueset/v2-tables.xml");
terminologyResources.add("/org/hl7/fhir/dstu3/model/valueset/v3-codesystems.xml");
structureDefinitionResources.add("/org/hl7/fhir/dstu3/model/profile/profiles-resources.xml");
structureDefinitionResources.add("/org/hl7/fhir/dstu3/model/profile/profiles-types.xml");
structureDefinitionResources.add("/org/hl7/fhir/dstu3/model/profile/profiles-others.xml");
structureDefinitionResources.add("/org/hl7/fhir/dstu3/model/extension/extension-definitions.xml");
break;
case R4:
terminologyResources.add("/org/hl7/fhir/r4/model/valueset/valuesets.xml");
terminologyResources.add("/org/hl7/fhir/r4/model/valueset/v2-tables.xml");
terminologyResources.add("/org/hl7/fhir/r4/model/valueset/v3-codesystems.xml");
structureDefinitionResources.add("/org/hl7/fhir/r4/model/profile/profiles-resources.xml");
structureDefinitionResources.add("/org/hl7/fhir/r4/model/profile/profiles-types.xml");
structureDefinitionResources.add("/org/hl7/fhir/r4/model/profile/profiles-others.xml");
structureDefinitionResources.add("/org/hl7/fhir/r4/model/extension/extension-definitions.xml");
break;
case R4B:
terminologyResources.add("/org/hl7/fhir/r4b/model/valueset/valuesets.xml");
terminologyResources.add("/org/hl7/fhir/r4b/model/valueset/v2-tables.xml");
terminologyResources.add("/org/hl7/fhir/r4b/model/valueset/v3-codesystems.xml");
structureDefinitionResources.add("/org/hl7/fhir/r4b/model/profile/profiles-resources.xml");
structureDefinitionResources.add("/org/hl7/fhir/r4b/model/profile/profiles-types.xml");
structureDefinitionResources.add("/org/hl7/fhir/r4b/model/profile/profiles-others.xml");
structureDefinitionResources.add("/org/hl7/fhir/r4b/model/extension/extension-definitions.xml");
break;
case R5:
structureDefinitionResources.add("/org/hl7/fhir/r5/model/profile/profiles-resources.xml");
structureDefinitionResources.add("/org/hl7/fhir/r5/model/profile/profiles-types.xml");
structureDefinitionResources.add("/org/hl7/fhir/r5/model/profile/profiles-others.xml");
structureDefinitionResources.add("/org/hl7/fhir/r5/model/extension/extension-definitions.xml");
terminologyResources.add("/org/hl7/fhir/r5/model/valueset/valuesets.xml");
terminologyResources.add("/org/hl7/fhir/r5/model/valueset/v2-tables.xml");
terminologyResources.add("/org/hl7/fhir/r5/model/valueset/v3-codesystems.xml");
break;
}
myTerminologyResources = terminologyResources;
myStructureDefinitionResources = structureDefinitionResources;
}
@Override
public List fetchAllConformanceResources() {
ArrayList retVal = new ArrayList<>();
retVal.addAll(myCodeSystems.values());
retVal.addAll(myStructureDefinitions.values());
retVal.addAll(myValueSets.values());
return retVal;
}
@Override
public List fetchAllStructureDefinitions() {
return toList(provideStructureDefinitionMap());
}
@Nullable
@Override
public List fetchAllNonBaseStructureDefinitions() {
return null;
}
@Override
public IBaseResource fetchCodeSystem(String theSystem) {
return fetchCodeSystemOrValueSet(theSystem, true);
}
private IBaseResource fetchCodeSystemOrValueSet(String theSystem, boolean codeSystem) {
synchronized (this) {
Map codeSystems = myCodeSystems;
Map valueSets = myValueSets;
if (codeSystems == null || valueSets == null) {
codeSystems = new HashMap<>();
valueSets = new HashMap<>();
initializeResourceLists();
for (String next : myTerminologyResources) {
loadCodeSystems(codeSystems, valueSets, next);
}
myCodeSystems = codeSystems;
myValueSets = valueSets;
}
// System can take the form "http://url|version"
String system = theSystem;
String version = null;
int pipeIdx = system.indexOf('|');
if (pipeIdx > 0) {
version = system.substring(pipeIdx + 1);
system = system.substring(0, pipeIdx);
}
IBaseResource candidate;
if (codeSystem) {
candidate = codeSystems.get(system);
} else {
candidate = valueSets.get(system);
}
if (candidate != null
&& isNotBlank(version)
&& !system.startsWith("http://hl7.org")
&& !system.startsWith("http://terminology.hl7.org")) {
if (!StringUtils.equals(
version, myCtx.newTerser().getSinglePrimitiveValueOrNull(candidate, "version"))) {
candidate = null;
}
}
return candidate;
}
}
@Override
public IBaseResource fetchStructureDefinition(String theUrl) {
String url = theUrl;
if (!url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) {
if (url.indexOf('/') == -1) {
url = URL_PREFIX_STRUCTURE_DEFINITION + url;
} else if (StringUtils.countMatches(url, '/') == 1) {
url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url;
}
}
Map structureDefinitionMap = provideStructureDefinitionMap();
IBaseResource retVal = structureDefinitionMap.get(url);
if (retVal == null) {
if (url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) {
/*
* A few built-in R4 SearchParameters have the wrong casing for primitive
* search parameters eg "value.as(String)" when it should be
* "value.as(string)". This lets us be a bit lenient about this.
*/
if (myCtx.getVersion().getVersion() == FhirVersionEnum.R4
|| myCtx.getVersion().getVersion() == FhirVersionEnum.R4B
|| myCtx.getVersion().getVersion() == FhirVersionEnum.R5) {
String end = url.substring(URL_PREFIX_STRUCTURE_DEFINITION.length());
if (Character.isUpperCase(end.charAt(0))) {
String newEnd = Character.toLowerCase(end.charAt(0)) + end.substring(1);
String alternateUrl = URL_PREFIX_STRUCTURE_DEFINITION + newEnd;
retVal = structureDefinitionMap.get(alternateUrl);
if (retVal != null) {
retVal = myCtx.newTerser().clone(retVal);
myCtx.newTerser().setElement(retVal, "type", end);
}
}
}
}
}
return retVal;
}
@Override
public IBaseResource fetchValueSet(String theUrl) {
IBaseResource retVal = fetchCodeSystemOrValueSet(theUrl, false);
return retVal;
}
public void flush() {
myCodeSystems = null;
myStructureDefinitions = null;
}
@Override
public FhirContext getFhirContext() {
return myCtx;
}
private Map provideStructureDefinitionMap() {
Map structureDefinitions = myStructureDefinitions;
if (structureDefinitions == null) {
structureDefinitions = new HashMap<>();
initializeResourceLists();
for (String next : myStructureDefinitionResources) {
loadStructureDefinitions(structureDefinitions, next);
}
myStructureDefinitions = structureDefinitions;
}
return structureDefinitions;
}
private void loadCodeSystems(
Map theCodeSystems, Map theValueSets, String theClasspath) {
ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath);
InputStream inputStream = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
InputStreamReader reader = null;
if (inputStream != null) {
try {
reader = new InputStreamReader(inputStream, Constants.CHARSET_UTF8);
List resources = parseBundle(reader);
for (IBaseResource next : resources) {
RuntimeResourceDefinition nextDef = getFhirContext().getResourceDefinition(next);
Map map = null;
switch (nextDef.getName()) {
case "CodeSystem":
map = theCodeSystems;
break;
case "ValueSet":
map = theValueSets;
break;
}
if (map != null) {
String urlValueString = getConformanceResourceUrl(next);
if (isNotBlank(urlValueString)) {
map.put(urlValueString, next);
}
switch (myCtx.getVersion().getVersion()) {
case DSTU2:
case DSTU2_HL7ORG:
IPrimitiveType> codeSystem = myCtx.newTerser()
.getSingleValueOrNull(next, "ValueSet.codeSystem.system", IPrimitiveType.class);
if (codeSystem != null && isNotBlank(codeSystem.getValueAsString())) {
theCodeSystems.put(codeSystem.getValueAsString(), next);
}
break;
default:
case DSTU2_1:
case DSTU3:
case R4:
case R4B:
case R5:
break;
}
}
}
} finally {
try {
if (reader != null) {
reader.close();
}
inputStream.close();
} catch (IOException e) {
ourLog.warn("Failure closing stream", e);
}
}
} else {
ourLog.warn("Unable to load resource: {}", theClasspath);
}
// Load built-in system
if (myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
String storageCodeEnum =
ClasspathUtil.loadResource("ca/uhn/fhir/context/support/HapiFhirStorageResponseCode.json");
IBaseResource storageCodeCodeSystem = myCtx.newJsonParser()
.setParserErrorHandler(new LenientErrorHandler())
.parseResource(storageCodeEnum);
String url = myCtx.newTerser().getSinglePrimitiveValueOrNull(storageCodeCodeSystem, "url");
theCodeSystems.put(url, storageCodeCodeSystem);
}
}
private void loadStructureDefinitions(Map theCodeSystems, String theClasspath) {
ourLog.info("Loading structure definitions from classpath: {}", theClasspath);
String packageUserData = null;
if (myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
packageUserData = "hl7.fhir."
+ myCtx.getVersion()
.getVersion()
.name()
.replace("DSTU", "R")
.toLowerCase(Locale.US);
}
try (InputStream valueSetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath)) {
if (valueSetText != null) {
try (InputStreamReader reader = new InputStreamReader(valueSetText, Constants.CHARSET_UTF8)) {
List resources = parseBundle(reader);
for (IBaseResource next : resources) {
String nextType = getFhirContext().getResourceType(next);
if ("StructureDefinition".equals(nextType)) {
String url = getConformanceResourceUrl(next);
if (isNotBlank(url)) {
theCodeSystems.put(url, next);
}
}
// This is used by the validator to determine which package a given SD came from.
// I don't love this use of magic strings but that's what is expected currently
if (packageUserData != null) {
next.setUserData("package", packageUserData);
}
}
}
} else {
ourLog.warn("Unable to load resource: {}", theClasspath);
}
} catch (IOException theE) {
ourLog.warn("Unable to load resource: {}", theClasspath);
}
}
private String getConformanceResourceUrl(IBaseResource theResource) {
return DefaultProfileValidationSupport.getConformanceResourceUrl(getFhirContext(), theResource);
}
private List parseBundle(InputStreamReader theReader) {
IBaseResource parsedObject = getFhirContext().newXmlParser().parseResource(theReader);
if (parsedObject instanceof IBaseBundle) {
IBaseBundle bundle = (IBaseBundle) parsedObject;
return BundleUtil.toListOfResources(getFhirContext(), bundle);
} else {
return Collections.singletonList(parsedObject);
}
}
static List toList(Map theMap) {
ArrayList retVal = new ArrayList<>(theMap.values());
return (List) Collections.unmodifiableList(retVal);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy