Add support for CodeSystem supplements and correctly processing extensions when doing expansions

This commit is contained in:
Grahame Grieve 2023-04-03 20:47:23 +10:00
parent 548284fcda
commit 7d4cb4b5a2
11 changed files with 549 additions and 29 deletions

View File

@ -657,6 +657,30 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
}
return cs;
}
@Override
public CodeSystem fetchSupplementedCodeSystem(String system) {
CodeSystem cs = fetchCodeSystem(system);
if (cs != null) {
List<CodeSystem> supplements = codeSystems.getSupplements(cs);
if (supplements.size() > 0) {
cs = CodeSystemUtilities.mergeSupplements(cs, supplements);
}
}
return cs;
}
@Override
public CodeSystem fetchSupplementedCodeSystem(String system, String version) {
CodeSystem cs = fetchCodeSystem(system, version);
if (cs != null) {
List<CodeSystem> supplements = codeSystems.getSupplements(cs);
if (supplements.size() > 0) {
cs = CodeSystemUtilities.mergeSupplements(cs, supplements);
}
}
return cs;
}
@Override
public boolean supportsSystem(String system) throws TerminologyServiceException {
@ -846,7 +870,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT);
return res;
}
if (res.getErrorClass() == TerminologyServiceErrorClass.INTERNAL_ERROR) { // this class is created specifically to say: don't consult the server
if (res.getErrorClass() == TerminologyServiceErrorClass.INTERNAL_ERROR || isNoTerminologyServer()) { // this class is created specifically to say: don't consult the server
return new ValueSetExpansionOutcome(res.getError(), res.getErrorClass());
}

View File

@ -30,14 +30,16 @@ public class CanonicalResourceManager<T extends CanonicalResource> {
private String id;
private String url;
private String version;
private String supplements;
private CanonicalResource resource;
public CanonicalResourceProxy(String type, String id, String url, String version) {
public CanonicalResourceProxy(String type, String id, String url, String version, String supplements) {
super();
this.type = type;
this.id = id;
this.url = url;
this.version = version;
this.supplements = supplements;
}
public String getType() {
@ -68,6 +70,10 @@ public class CanonicalResourceManager<T extends CanonicalResource> {
return version != null;
}
public String getSupplements() {
return supplements;
}
public CanonicalResource getResource() throws FHIRException {
if (resource == null) {
resource = loadResource();
@ -111,7 +117,7 @@ public class CanonicalResourceManager<T extends CanonicalResource> {
private T1 resource;
private CanonicalResourceProxy proxy;
private PackageInformation packageInfo;
public CachedCanonicalResource(T1 resource, PackageInformation packageInfo) {
super();
this.resource = resource;
@ -159,6 +165,14 @@ public class CanonicalResourceManager<T extends CanonicalResource> {
@Override
public String toString() {
return resource != null ? resource.fhirType()+"/"+resource.getId()+"["+resource.getUrl()+"|"+resource.getVersion()+"]" : proxy.toString();
}
public String supplements() {
if (resource == null) {
return proxy.getSupplements();
} else {
return resource instanceof CodeSystem ? ((CodeSystem) resource).getSupplements() : null;
}
}
}
@ -191,6 +205,7 @@ public class CanonicalResourceManager<T extends CanonicalResource> {
private Map<String, List<CachedCanonicalResource<T>>> listForId = new HashMap<>();
private Map<String, List<CachedCanonicalResource<T>>> listForUrl = new HashMap<>();
private Map<String, CachedCanonicalResource<T>> map = new HashMap<>();
private Map<String, List<CachedCanonicalResource<T>>> supplements = new HashMap<>(); // general index based on CodeSystem.supplements
private String version; // for debugging purposes
@ -288,6 +303,7 @@ public class CanonicalResourceManager<T extends CanonicalResource> {
if (!listForUrl.containsKey(cr.getUrl())) {
listForUrl.put(cr.getUrl(), new ArrayList<>());
}
addToSupplements(cr);
List<CachedCanonicalResource<T>> set = listForUrl.get(cr.getUrl());
set.add(cr);
Collections.sort(set, new MetadataResourceVersionComparator<CachedCanonicalResource<T>>());
@ -324,10 +340,27 @@ public class CanonicalResourceManager<T extends CanonicalResource> {
}
}
private void addToSupplements(CanonicalResourceManager<T>.CachedCanonicalResource<T> cr) {
String surl = cr.supplements();
if (surl != null) {
List<CanonicalResourceManager<T>.CachedCanonicalResource<T>> list = supplements.get(surl);
if (list == null) {
list = new ArrayList<>();
supplements.put(surl, list);
}
list.add(cr);
}
}
public void drop(CachedCanonicalResource<T> cr) {
while (map.values().remove(cr));
while (listForId.values().remove(cr));
while (listForUrl.values().remove(cr));
String surl = cr.supplements();
if (surl != null) {
supplements.get(surl).remove(cr);
}
list.remove(cr);
List<CachedCanonicalResource<T>> set = listForUrl.get(cr.getUrl());
if (set != null) { // it really should be
@ -514,6 +547,54 @@ public class CanonicalResourceManager<T extends CanonicalResource> {
}
}
public List<T> getSupplements(T cr) {
if (cr.hasSourcePackage()) {
List<String> pvl = new ArrayList<>();
pvl.add(cr.getSourcePackage().getVID());
return getSupplements(cr.getUrl(), cr.getVersion(), pvl);
} else {
return getSupplements(cr.getUrl(), cr.getVersion(), null);
}
}
public List<T> getSupplements(String url) {
return getSupplements(url, null, null);
}
public List<T> getSupplements(String url, String version) {
return getSupplements(url, version, null);
}
public List<T> getSupplements(String url, String version, List<String> pvlist) {
boolean possibleMatches = false;
List<T> res = new ArrayList<>();
if (version != null) {
List<CanonicalResourceManager<T>.CachedCanonicalResource<T>> list = supplements.get(url+"|"+version);
if (list != null) {
for (CanonicalResourceManager<T>.CachedCanonicalResource<T> t : list) {
possibleMatches = true;
if (pvlist == null || pvlist.contains(t.getPackageInfo().getVID())) {
res.add(t.getResource());
}
}
}
}
List<CanonicalResourceManager<T>.CachedCanonicalResource<T>> list = supplements.get(url);
if (list != null) {
for (CanonicalResourceManager<T>.CachedCanonicalResource<T> t : list) {
possibleMatches = true;
if (pvlist == null || pvlist.contains(t.getPackageInfo().getVID())) {
res.add(t.getResource());
}
}
}
if (res.isEmpty() && pvlist != null && possibleMatches) {
return getSupplements(url, version, null);
} else {
return res;
}
}
public void clear() {
list.clear();
map.clear();

View File

@ -544,6 +544,14 @@ public interface IWorkerContext {
public CodeSystem fetchCodeSystem(String system);
public CodeSystem fetchCodeSystem(String system, String version);
/**
* Like fetchCodeSystem, except that the context will find any CodeSysetm supplements and merge them into the
* @param system
* @return
*/
public CodeSystem fetchSupplementedCodeSystem(String system);
public CodeSystem fetchSupplementedCodeSystem(String system, String version);
/**
* True if the underlying terminology service provider will do
* expansion and code validation for the terminology. Corresponds

View File

@ -97,7 +97,7 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
private final IContextResourceLoader loader;
public PackageResourceLoader(PackageResourceInformation pri, IContextResourceLoader loader) {
super(pri.getResourceType(), pri.getId(), loader == null ? pri.getUrl() :loader.patchUrl(pri.getUrl(), pri.getResourceType()), pri.getVersion());
super(pri.getResourceType(), pri.getId(), loader == null ? pri.getUrl() :loader.patchUrl(pri.getUrl(), pri.getResourceType()), pri.getVersion(), pri.getSupplements());
this.filename = pri.getFilename();
this.loader = loader;
}

View File

@ -12,6 +12,9 @@ import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.Element;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.Property;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.utilities.Utilities;
public class ExtensionsUtils {
@ -275,4 +278,103 @@ public class ExtensionsUtils {
return result;
}
public static boolean stripExtensions(Element element, String... exceptions) {
return stripExtensions(element, Utilities.strings(exceptions));
}
public static boolean stripExtensions(Element element, List<String> exceptions) {
boolean res = element.getExtension().removeIf(ex -> !exceptions.contains(ex.getUrl()));
if (element instanceof BackboneElement) {
res = ((BackboneElement) element).getModifierExtension().removeIf(ex -> !exceptions.contains(ex.getUrl())) || res;
}
if (element instanceof BackboneElement) {
res = ((BackboneElement) element).getModifierExtension().removeIf(ex -> !exceptions.contains(ex.getUrl())) || res;
}
for (Property p : element.children()) {
for (Base v : p.getValues()) {
if (v instanceof Element) {
res = stripExtensions((Element) v, exceptions) || res;
} else if (v instanceof Element) {
res = stripExtensions((Resource) v, exceptions) || res;
}
}
}
return res;
}
public static boolean stripExtensions(Resource resource, String... exceptions) {
return stripExtensions(resource, Utilities.strings(exceptions));
}
public static boolean stripExtensions(Resource resource, List<String> exceptions) {
boolean res = false;
if (resource instanceof DomainResource) {
res = ((DomainResource) resource).getExtension().removeIf(ex -> !exceptions.contains(ex.getUrl())) ||
((DomainResource) resource).getModifierExtension().removeIf(ex -> !exceptions.contains(ex.getUrl()));
}
for (Property p : resource.children()) {
for (Base v : p.getValues()) {
if (v instanceof Element) {
res = stripExtensions((Element) v, exceptions) || res;
} else if (v instanceof Element) {
res = stripExtensions((Resource) v, exceptions) || res;
}
}
}
return res;
}
public static void copyExtensions(List<Extension> source, List<Extension> dest, String... urls) {
if (source != null && dest != null) {
for (Extension ex : source) {
if (Utilities.existsInList(ex.getUrl(), urls)) {
dest.add(ex.copy());
}
}
}
}
public static DataType getExtensionValue(List<Extension> extensions, String url) {
for (Extension ex : extensions) {
if (ex.getUrl().equals(url)) {
return ex.getValue();
}
}
return null;
}
public static String getExtensionString(List<Extension> extensions, String url) {
for (Extension ex : extensions) {
if (ex.getUrl().equals(url)) {
return ex.getValue().primitiveValue();
}
}
return null;
}
public static Integer getExtensionInteger(List<Extension> extensions, String url) {
for (Extension ex : extensions) {
if (ex.getUrl().equals(url) && ex.hasValueIntegerType()) {
return ex.getValueIntegerType().getValue();
}
}
return null;
}
public static boolean hasExtension(List<Extension> extensions, String url) {
if (extensions == null) {
return false;
}
for (Extension ex : extensions) {
if (ex.getUrl().equals(url)) {
return true;
}
}
return false;
}
}

View File

@ -57,6 +57,7 @@ import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.DateTimeType;
import org.hl7.fhir.r5.model.DecimalType;
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.IntegerType;
@ -64,6 +65,7 @@ import org.hl7.fhir.r5.model.Meta;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.StandardsStatus;
import org.hl7.fhir.utilities.Utilities;
@ -246,7 +248,7 @@ public class CodeSystemUtilities {
throw new Error("Unknown property type "+value.getClass().getName());
}
private static void defineProperty(CodeSystem cs, String code, PropertyType pt) {
private static String defineProperty(CodeSystem cs, String code, PropertyType pt) {
String url = "http://hl7.org/fhir/concept-properties#"+code;
for (PropertyComponent p : cs.getProperty()) {
if (p.getCode().equals(code)) {
@ -256,11 +258,11 @@ public class CodeSystemUtilities {
if (!p.getType().equals(pt)) {
throw new Error("Type mismatch for code "+code+" type = "+p.getType()+" vs "+pt);
}
return;
return code;
}
}
cs.addProperty().setCode(code).setUri(url).setType(pt).setUri(url);
return code;
}
public static void defineNotSelectableProperty(CodeSystem cs) {
@ -720,5 +722,101 @@ public class CodeSystemUtilities {
}
return t;
}
public static CodeSystem mergeSupplements(CodeSystem cs, List<CodeSystem> supplements) {
CodeSystem ret = cs.copy();
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (CodeSystem sup : supplements) {
b.append(sup.getVersionedUrl());
}
ret.setUserData("supplements.installed", b.toString());
for (ConceptDefinitionComponent t : ret.getConcept()) {
mergeSupplements(ret, t, supplements);
}
return ret;
}
private static void mergeSupplements(CodeSystem ret, ConceptDefinitionComponent fdef, List<CodeSystem> supplements) {
for (CodeSystem cs : supplements) {
ConceptDefinitionComponent def = CodeSystemUtilities.findCode(cs.getConcept(), fdef.getCode());
if (def != null) {
for (Extension ext : def.getExtension()) {
fdef.addExtension(ext.copy());
}
for (ConceptDefinitionDesignationComponent d : def.getDesignation()) {
fdef.addDesignation(d.copy());
}
for (ConceptPropertyComponent p : def.getProperty()) {
PropertyComponent pd = CodeSystemUtilities.getPropertyDefinition(cs, p);
String code;
if (pd != null) {
code = defineProperty(ret, pd, propertyTypeForType(p.getValue()));
} else {
code = defineProperty(ret, p.getCode(), propertyTypeForType(p.getValue()));
}
fdef.addProperty().setCode(code).setValue(p.getValue());
}
}
for (ConceptDefinitionComponent t : fdef.getConcept()) {
mergeSupplements(ret, t, supplements);
}
}
}
private static PropertyType propertyTypeForType(DataType value) {
if (value == null) {
return PropertyType.NULL;
}
if (value instanceof CodeType) {
return PropertyType.CODE;
}
if (value instanceof CodeType) {
return PropertyType.CODING;
}
if (value instanceof CodeType) {
return PropertyType.STRING;
}
if (value instanceof CodeType) {
return PropertyType.INTEGER;
}
if (value instanceof CodeType) {
return PropertyType.BOOLEAN;
}
if (value instanceof CodeType) {
return PropertyType.DATETIME;
}
if (value instanceof CodeType) {
return PropertyType.DECIMAL;
}
throw new FHIRException("Unsupported property value for a CodeSystem Property: "+value.fhirType());
}
private static String defineProperty(CodeSystem cs, PropertyComponent pd, PropertyType pt) {
for (PropertyComponent p : cs.getProperty()) {
if (p.getCode().equals(pd.getCode())) {
if (!p.getUri().equals(pd.getUri())) {
throw new Error("URI mismatch for code "+pd.getCode()+" url = "+p.getUri()+" vs "+pd.getUri());
}
if (!p.getType().equals(pt)) {
throw new Error("Type mismatch for code "+pd.getCode()+" type = "+p.getType().toCode()+" vs "+pt.toCode());
}
return pd.getCode();
}
}
cs.addProperty().setCode(pd.getCode()).setUri(pd.getUri()).setType(pt);
return pd.getCode();
}
private static PropertyComponent getPropertyDefinition(CodeSystem cs, ConceptPropertyComponent p) {
for (PropertyComponent t : cs.getProperty()) {
if (t.getCode().equals(p.getCode())) {
return t;
}
}
return null;
}
}

View File

@ -41,7 +41,7 @@ import org.hl7.fhir.r5.model.ValueSet;
public interface ValueSetExpander {
public enum TerminologyServiceErrorClass {
UNKNOWN, NOSERVICE, SERVER_ERROR, VALUESET_UNSUPPORTED, CODESYSTEM_UNSUPPORTED, BLOCKED_BY_OPTIONS, INTERNAL_ERROR;
UNKNOWN, NOSERVICE, SERVER_ERROR, VALUESET_UNSUPPORTED, CODESYSTEM_UNSUPPORTED, BLOCKED_BY_OPTIONS, INTERNAL_ERROR, BUSINESS_RULE;
public boolean isInfrastructure() {
return this == NOSERVICE || this == SERVER_ERROR || this == VALUESET_UNSUPPORTED;

View File

@ -82,7 +82,9 @@ import org.hl7.fhir.exceptions.NoTerminologyServiceException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.LangaugeUtils;
import org.hl7.fhir.r5.extensions.ExtensionConstants;
import org.hl7.fhir.r5.extensions.Extensions;
import org.hl7.fhir.r5.extensions.ExtensionsUtils;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
@ -206,6 +208,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
private ValueSet focus;
private int maxExpansionSize = 500;
private List<String> allErrors = new ArrayList<>();
private List<String> requiredSupplements = new ArrayList<>();
private int total;
private boolean checkCodesWhenExpanding;
@ -227,7 +230,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
private ValueSetExpansionContainsComponent addCode(String system, String code, String display, String dispLang, ValueSetExpansionContainsComponent parent, List<ConceptDefinitionDesignationComponent> designations, Parameters expParams,
boolean isAbstract, boolean inactive, String definition, List<ValueSet> filters, boolean noInactive, boolean deprecated, List<ValueSetExpansionPropertyComponent> vsProp,
List<ConceptPropertyComponent> csProps, List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> expProps) {
List<ConceptPropertyComponent> csProps, List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> expProps, List<Extension> csExtList, List<Extension> vsExtList) {
if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code))
return null;
@ -248,6 +251,37 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
if (expParams.getParameterBool("includeDefinition") && definition != null) {
n.addExtension(Extensions.makeVSConceptDefinition(definition));
}
if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-label")) {
ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#label", "label", ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-label"));
}
if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-label")) {
ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#label", "label", ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-label"));
}
if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-conceptOrder")) {
ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#order", "order", ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/codesystem-conceptOrder"));
}
if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-conceptOrder")) {
ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#order", "order", ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/valueset-conceptOrder"));
}
if (ExtensionsUtils.hasExtension(csExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")) {
ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#itemWeight", "weight", ExtensionsUtils.getExtensionValue(csExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight"));
}
if (ExtensionsUtils.hasExtension(vsExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight")) {
ValueSetUtilities.addProperty(focus, n, "http://hl7.org/fhir/concept-properties#itemWeight", "weight", ExtensionsUtils.getExtensionValue(vsExtList, "http://hl7.org/fhir/StructureDefinition/itemWeight"));
}
ExtensionsUtils.copyExtensions(csExtList, n.getExtension(),
"http://hl7.org/fhir/StructureDefinition/coding-sctdescid",
"http://hl7.org/fhir/StructureDefinition/rendering-style",
"http://hl7.org/fhir/StructureDefinition/rendering-xhtml",
"http://hl7.org/fhir/StructureDefinition/codesystem-alternate");
ExtensionsUtils.copyExtensions(vsExtList, n.getExtension(),
"http://hl7.org/fhir/StructureDefinition/valueset-supplement",
"http://hl7.org/fhir/StructureDefinition/valueset-deprecated",
"http://hl7.org/fhir/StructureDefinition/valueset-concept-definition",
"http://hl7.org/fhir/StructureDefinition/coding-sctdescid",
"http://hl7.org/fhir/StructureDefinition/rendering-style",
"http://hl7.org/fhir/StructureDefinition/rendering-xhtml");
// display and designations
String srcLang = dispLang;
@ -349,7 +383,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
private void addCodeAndDescendents(ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc) throws FHIRException {
focus.checkNoModifiers("Expansion.contains", "expanding");
ValueSetExpansionContainsComponent np = addCode(focus.getSystem(), focus.getCode(), focus.getDisplay(), vsSrc.getLanguage(), parent,
convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), focus.getExtensionString(ToolingExtensions.EXT_DEFINITION), filters, noInactive, false, vsProps, null, focus.getProperty());
convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), focus.getExtensionString(ToolingExtensions.EXT_DEFINITION), filters, noInactive, false, vsProps, null, focus.getProperty(), null, focus.getExtension());
for (ValueSetExpansionContainsComponent c : focus.getContains())
addCodeAndDescendents(focus, np, expParams, filters, noInactive, vsProps, vsSrc);
}
@ -378,7 +412,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
boolean inc = CodeSystemUtilities.isInactive(cs, def);
boolean dep = CodeSystemUtilities.isDeprecated(cs, def, false);
if ((includeAbstract || !abs) && filterFunc.includeConcept(cs, def)) {
np = addCode(system, def.getCode(), def.getDisplay(), cs.getLanguage(), parent, def.getDesignation(), expParams, abs, inc, def.getDefinition(), filters, noInactive, dep, vsProps, def.getProperty(), null);
np = addCode(system, def.getCode(), def.getDisplay(), cs.getLanguage(), parent, def.getDesignation(), expParams, abs, inc, def.getDefinition(), filters, noInactive, dep, vsProps, def.getProperty(), null, def.getExtension(), null);
}
for (ConceptDefinitionComponent c : def.getConcept()) {
addCodeAndDescendents(cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps);
@ -422,7 +456,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
throw fail("Processing Value set references in exclude is not yet done in "+ctxt);
// importValueSet(imp.getValue(), params, expParams);
CodeSystem cs = context.fetchCodeSystem(exc.getSystem());
CodeSystem cs = context.fetchSupplementedCodeSystem(exc.getSystem());
if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem())) {
ValueSetExpansionOutcome vse = context.expandVS(exc, false, false);
ValueSet valueset = vse.getValueset();
@ -493,11 +527,15 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
focus.getExpansion().addParameter().setName(p.getName()).setValue(p.getValue());
}
}
for (Extension s : focus.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) {
requiredSupplements.add(s.getValue().primitiveValue());
}
if (expParams.hasLanguage()) {
focus.setLanguage(expParams.getLanguage());
}
if (source.hasCompose()) {
ExtensionsUtils.stripExtensions(focus.getCompose());
handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension(), source);
}
@ -517,6 +555,9 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
if (total > 0) {
focus.getExpansion().setTotal(total);
}
if (!requiredSupplements.isEmpty()) {
return new ValueSetExpansionOutcome("Required supplements not found: "+requiredSupplements.toString(), TerminologyServiceErrorClass.BUSINESS_RULE, allErrors);
}
return new ValueSetExpansionOutcome(focus);
}
@ -656,7 +697,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
for (ValueSetExpansionContainsComponent c : list) {
c.checkNoModifiers("Imported Expansion in Code System", "expanding");
ValueSetExpansionContainsComponent np = addCode(c.getSystem(), c.getCode(), c.getDisplay(), vsSrc.getLanguage(), parent, null, expParams, c.getAbstract(), c.getInactive(), c.getExtensionString(ToolingExtensions.EXT_DEFINITION),
filter, noInactive, false, vsProps, null, c.getProperty());
filter, noInactive, false, vsProps, null, c.getProperty(), null, c.getExtension());
copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc);
}
}
@ -676,10 +717,15 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
base.checkNoModifiers("Imported ValueSet", "expanding");
copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base);
} else {
CodeSystem cs = context.fetchCodeSystem(inc.getSystem());
CodeSystem cs = context.fetchSupplementedCodeSystem(inc.getSystem());
if (isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) {
doServerIncludeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, valueSet.getExpansion().getProperty());
} else {
if (cs.hasUserData("supplements.installed")) {
for (String s : cs.getUserString("supplements.installed").split("\\,")) {
requiredSupplements.remove(s);
}
}
doInternalIncludeCodes(inc, exp, expParams, imports, cs, noInactive, valueSet);
}
}
@ -709,7 +755,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
}
for (Extension ex : vs.getExpansion().getExtension()) {
if (Utilities.existsInList(ex.getUrl(), ToolingExtensions.EXT_EXP_TOOCOSTLY, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed")) {
if (!hasExtension(extensions, ex.getUrl())) {
if (!ExtensionsUtils.hasExtension(extensions, ex.getUrl())) {
extensions.add(ex);
}
}
@ -719,14 +765,6 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
}
}
private boolean hasExtension(List<Extension> extensions, String url) {
for (Extension ex : extensions) {
if (ex.getUrl().equals(url)) {
return true;
}
}
return false;
}
public void doInternalIncludeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, List<ValueSet> imports, CodeSystem cs, boolean noInactive, Resource vsSrc) throws NoTerminologyServiceException, TerminologyServiceException, FHIRException {
if (cs == null) {
@ -758,10 +796,11 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
if (!inc.getConcept().isEmpty()) {
canBeHeirarchy = false;
for (ConceptReferenceComponent c : inc.getConcept()) {
c.checkNoModifiers("Code in Code System", "expanding");
c.checkNoModifiers("Code in Value Set", "expanding");
ConceptDefinitionComponent def = CodeSystemUtilities.findCode(cs.getConcept(), c.getCode());
Boolean inactive = false; // default is true if we're a fragment and
if (def == null) {
def.checkNoModifiers("Code in Code System", "expanding");
if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
addFragmentWarning(exp, cs);
} else if (cs.getContent() == CodeSystemContentMode.EXAMPLE) {
@ -775,7 +814,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
inactive = CodeSystemUtilities.isInactive(cs, def);
}
addCode(inc.getSystem(), c.getCode(), !Utilities.noString(c.getDisplay()) ? c.getDisplay() : def == null ? null : def.getDisplay(), c.hasDisplay() ? vsSrc.getLanguage() : cs.getLanguage(), null, mergeDesignations(def, convertDesignations(c.getDesignation())),
expParams, false, inactive, def == null ? null : def.getDefinition(), imports, noInactive, false, exp.getProperty(), def != null ? def.getProperty() : null, null);
expParams, false, inactive, def == null ? null : def.getDefinition(), imports, noInactive, false, exp.getProperty(), def != null ? def.getProperty() : null, null, def == null ? null : def.getExtension(), c.getExtension());
}
}
if (inc.getFilter().size() > 1) {
@ -822,7 +861,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
if (isNotBlank(def.getDisplay()) && isNotBlank(fc.getValue())) {
if (def.getDisplay().contains(fc.getValue())) {
addCode(inc.getSystem(), def.getCode(), def.getDisplay(), cs.getLanguage(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def),
def.getDefinition(), imports, noInactive, false, exp.getProperty(), def.getProperty(), null);
def.getDefinition(), imports, noInactive, false, exp.getProperty(), def.getProperty(), null, def.getExtension(), null);
}
}
}

View File

@ -42,6 +42,8 @@ import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.DateTimeType;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r5.model.Identifier;
@ -52,6 +54,7 @@ import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
@ -328,5 +331,54 @@ public class ValueSetUtilities {
return false;
}
public static void addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, String value) {
if (value != null) {
addProperty(vs, ctxt, url, code, new StringType(value));
}
}
public static void addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, Integer value) {
if (value != null) {
addProperty(vs, ctxt, url, code, new IntegerType(value));
}
}
public static void addProperty(ValueSet vs, ValueSetExpansionContainsComponent ctxt, String url, String code, DataType value) {
code = defineProperty(vs, url, code);
org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p = getProperty(ctxt.getProperty(), code);
if (p != null)
p.setValue(value);
else
ctxt.addProperty().setCode(code).setValue(value);
}
private static org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent getProperty(List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> list, String code) {
for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent t : list) {
if (code.equals(t.getCode())) {
return t;
}
}
return null;
}
private static String defineProperty(ValueSet vs, String url, String code) {
for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) {
if (p.hasUri() && p.getUri().equals(url)) {
return p.getCode();
}
}
for (ValueSetExpansionPropertyComponent p : vs.getExpansion().getProperty()) {
if (p.hasCode() && p.getCode().equals(code)) {
p.setUri(url);
return code;
}
}
ValueSetExpansionPropertyComponent p = vs.getExpansion().addProperty();
p.setUri(url);
p.setCode(code);
return code;
}
}

View File

@ -7,6 +7,7 @@ import java.util.List;
import org.hl7.fhir.r5.context.CanonicalResourceManager;
import org.hl7.fhir.r5.context.CanonicalResourceManager.CanonicalResourceProxy;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.PackageInformation;
import org.hl7.fhir.r5.model.ValueSet;
import org.junit.jupiter.api.Assertions;
@ -18,7 +19,7 @@ public class CanonicalResourceManagerTests {
private CanonicalResource resource;
public DeferredLoadTestResource(CanonicalResource resource) {
super(resource.fhirType(), resource.getId(), resource.getUrl(), resource.getVersion());
super(resource.fhirType(), resource.getId(), resource.getUrl(), resource.getVersion(), resource instanceof CodeSystem ? ((CodeSystem) resource).getSupplements() : null);
this.resource = resource;
}
@ -853,4 +854,77 @@ public class CanonicalResourceManagerTests {
Assertions.assertEquals("2", mrm.get("http://url/ValueSet/234", "4.0.2", pvl2).getName());
}
@Test
public void testSupplements() {
CanonicalResourceManager<CodeSystem> mrm = new CanonicalResourceManager<>(true);
CodeSystem csb1 = new CodeSystem();
csb1.setId("2345");
csb1.setUrl("http://url/CodeSystem/234");
csb1.setVersion("4.0.1");
csb1.setName("1");
mrm.see(csb1, new PackageInformation("pid.one", "1.0.0", new Date()));
CodeSystem csb2 = new CodeSystem();
csb2.setId("2346");
csb2.setUrl("http://url/CodeSystem/234");
csb2.setVersion("4.0.1");
csb2.setName("2");
mrm.see(csb2, new PackageInformation("pid.two", "1.0.0", new Date()));
CodeSystem css1 = new CodeSystem();
css1.setId("s2345");
css1.setUrl("http://url/CodeSystem/s234");
css1.setVersion("4.0.1");
css1.setName("s1");
css1.setSupplements("http://url/CodeSystem/234");
mrm.see(css1, new PackageInformation("pid.one", "1.0.0", new Date()));
CodeSystem css2 = new CodeSystem();
css2.setId("s2346");
css2.setUrl("http://url/CodeSystem/s234");
css2.setVersion("4.0.1");
css2.setName("s2");
css2.setSupplements("http://url/CodeSystem/234");
mrm.see(css2, new PackageInformation("pid.two", "1.0.0", new Date()));
List<CodeSystem> sl = mrm.getSupplements("http://url/CodeSystem/234");
Assertions.assertEquals(2, sl.size());
sl = mrm.getSupplements("http://url/CodeSystem/234", "1.0.1");
Assertions.assertEquals(2, sl.size());
sl = mrm.getSupplements("http://url/CodeSystem/s234");
Assertions.assertEquals(0, sl.size());
List<String> pvl = new ArrayList<>();
pvl.add("pid.two#1.0.0");
sl = mrm.getSupplements("http://url/CodeSystem/234", "1.0.1", pvl);
Assertions.assertEquals(1, sl.size());
mrm.drop("s2346");
sl = mrm.getSupplements("http://url/CodeSystem/234");
Assertions.assertEquals(1, sl.size());
sl = mrm.getSupplements("http://url/CodeSystem/234", "1.0.1");
Assertions.assertEquals(1, sl.size());
sl = mrm.getSupplements("http://url/CodeSystem/s234");
Assertions.assertEquals(0, sl.size());
pvl = new ArrayList<>();
pvl.add("pid.two#1.0.0");
sl = mrm.getSupplements("http://url/CodeSystem/234", "1.0.1", pvl);
Assertions.assertEquals(1, sl.size()); // cause we fall back to the other
pvl = new ArrayList<>();
pvl.add("pid.one#1.0.0");
sl = mrm.getSupplements("http://url/CodeSystem/234", "1.0.1", pvl);
Assertions.assertEquals(1, sl.size());
mrm.drop("s2345");
mrm.drop("s2346");
sl = mrm.getSupplements("http://url/CodeSystem/234");
Assertions.assertEquals(0, sl.size());
sl = mrm.getSupplements("http://url/CodeSystem/234", "1.0.1");
Assertions.assertEquals(0, sl.size());
sl = mrm.getSupplements("http://url/CodeSystem/s234");
Assertions.assertEquals(0, sl.size());
}
}

View File

@ -22,6 +22,10 @@ import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.r5.model.OperationOutcome.IssueType;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent;
@ -103,7 +107,7 @@ public class TerminologyServiceTests {
@Test
public void test() throws Exception {
if (baseEngine == null) {
baseEngine = TestUtilities.getValidationEngine("hl7.fhir.r5.core#5.0.0", ValidationEngineTests.DEF_TX, null, FhirPublication.R5, true, "5.0.0");
baseEngine = TestUtilities.getValidationEngine("hl7.fhir.r5.core#5.0.0", null, null, FhirPublication.R5, true, "5.0.0");
}
ValidationEngine engine = new ValidationEngine(this.baseEngine);
for (String s : setup.suite.forceArray("setup").asStrings()) {
@ -149,7 +153,45 @@ public class TerminologyServiceTests {
Assertions.assertTrue(diff == null, diff);
}
} else {
Assertions.fail("expand error not done yet");
OperationOutcome oo = new OperationOutcome();
OperationOutcomeIssueComponent e = new OperationOutcomeIssueComponent();
e.setSeverity(IssueSeverity.ERROR);
switch (vse.getErrorClass()) {
case BLOCKED_BY_OPTIONS:
e.setCode(IssueType.FORBIDDEN);
break;
case BUSINESS_RULE:
e.setCode(IssueType.BUSINESSRULE);
break;
case CODESYSTEM_UNSUPPORTED:
e.setCode(IssueType.INVALID);
break;
case INTERNAL_ERROR:
e.setCode(IssueType.EXCEPTION);
break;
case NOSERVICE:
e.setCode(IssueType.CONFLICT);
break;
case SERVER_ERROR:
e.setCode(IssueType.EXCEPTION);
break;
case UNKNOWN:
e.setCode(IssueType.UNKNOWN);
break;
case VALUESET_UNSUPPORTED:
e.setCode(IssueType.NOTSUPPORTED);
break;
}
e.getDetails().setText(vse.getError());
oo.addIssue(e);
String ooj = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
String diff = CompareUtilities.checkJsonSrcIsSame(resp, ooj);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
TextFile.stringToFile(ooj, fp);
}
Assertions.assertTrue(diff == null, diff);
}
}