Fix for Jira 25179 - change handling of imports

See https://chat.fhir.org/#narrow/stream/179202-terminology/topic/ValueSet.20defined.20by.20an.20intersection.20.3F
This commit is contained in:
Grahame Grieve 2022-03-18 12:45:40 +11:00
parent e8aab6db0e
commit cf95c1a2ba
13 changed files with 128 additions and 21 deletions

View File

@ -130,6 +130,7 @@ public class CanonicalResourceManager<T extends CanonicalResource> {
synchronized (this) {
resource = res;
}
resource.setUserData("package", packageInfo);
proxy = null;
}
return resource;
@ -226,6 +227,10 @@ public class CanonicalResourceManager<T extends CanonicalResource> {
drop(cr.getId());
}
if (cr.resource != null) {
cr.resource.setUserData("package", cr.getPackageInfo());
}
// special case logic for UTG support prior to version 5
if (cr.getPackageInfo() != null && cr.getPackageInfo().getId().startsWith("hl7.terminology")) {
List<CachedCanonicalResource<T>> toDrop = new ArrayList<>();

View File

@ -3,6 +3,7 @@ package org.hl7.fhir.r5.context;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
/*
Copyright (c) 2011+, HL7, Inc.
@ -148,8 +149,9 @@ public interface IWorkerContext {
public class PackageVersion {
private String id;
private String version;
private Date date;
public PackageVersion(String source) {
public PackageVersion(String source, Date date) {
if (source == null) {
throw new Error("Source cannot be null");
}
@ -158,12 +160,15 @@ public interface IWorkerContext {
}
id = source.substring(0, source.indexOf("#"));
version = source.substring(source.indexOf("#")+1);
this.date = date;
}
public PackageVersion(String id, String version) {
public PackageVersion(String id, String version, Date date) {
super();
this.id = id;
this.version = version;
this.date = date;
}
public String getId() {
return id;
}
@ -178,6 +183,9 @@ public interface IWorkerContext {
public String toString() {
return id+"#"+version;
}
public Date getDate() {
return date;
}
}
@ -185,8 +193,9 @@ public interface IWorkerContext {
private String name;
private String canonical;
private String web;
public PackageDetails(String id, String version, String name, String canonical, String web) {
super(id, version);
public PackageDetails(String id, String version, String name, String canonical, String web, Date date) {
super(id, version, date);
this.name = name;
this.canonical = canonical;
this.web = web;

View File

@ -474,7 +474,7 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
}
for (String s : pi.listResources(types)) {
try {
loadDefinitionItem(s, pi.load("package", s), loader, null, new PackageVersion(pi.id(), pi.version()));
loadDefinitionItem(s, pi.load("package", s), loader, null, new PackageVersion(pi.id(), pi.version(), pi.dateAsDate()));
t++;
} catch (Exception e) {
throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, s, pi.name(), pi.version(), e.getMessage()), e);
@ -486,7 +486,7 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
}
for (PackageResourceInformation pri : pi.listIndexedResources(types)) {
try {
registerResourceFromPackage(new PackageResourceLoader(pri, loader), new PackageVersion(pi.id(), pi.version()));
registerResourceFromPackage(new PackageResourceLoader(pri, loader), new PackageVersion(pi.id(), pi.version(), pi.dateAsDate()));
t++;
} catch (FHIRException e) {
throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, pri.getFilename(), pi.name(), pi.version(), e.getMessage()), e);

View File

@ -32,6 +32,8 @@ package org.hl7.fhir.r5.terminologies;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -41,6 +43,8 @@ import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.NoTerminologyServiceException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContext.PackageDetails;
import org.hl7.fhir.r5.context.IWorkerContext.PackageVersion;
import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CodeSystem;
@ -64,6 +68,7 @@ import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.ValidationConte
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.npm.PackageInfo;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.validation.ValidationOptions.ValueSetMode;
@ -671,9 +676,17 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe
}
private Boolean inComponent(ConceptSetComponent vsi, int vsiIndex, String system, String code, boolean only, List<String> warnings) throws FHIRException {
for (UriType uri : vsi.getValueSet()) {
if (inImport(uri.getValue(), system, code)) {
return true;
if (isValueSetUnionImports()) {
for (UriType uri : vsi.getValueSet()) {
if (inImport(uri.getValue(), system, code)) {
return true;
}
}
} else {
for (UriType uri : vsi.getValueSet()) {
if (!inImport(uri.getValue(), system, code)) {
return false;
}
}
}
@ -738,6 +751,15 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe
}
}
protected boolean isValueSetUnionImports() {
PackageVersion p = (PackageVersion) valueset.getUserData("package");
if (p != null) {
return p.getDate().before(new GregorianCalendar(2022, Calendar.MARCH, 31).getTime());
} else {
return false;
}
}
private boolean codeInFilter(CodeSystem cs, String system, ConceptSetFilterComponent f, String code) throws FHIRException {
if ("concept".equals(f.getProperty()))
return codeInConceptFilter(cs, f, code);

View File

@ -66,7 +66,9 @@ import java.io.IOException;
*/
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -79,6 +81,7 @@ import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.exceptions.NoTerminologyServiceException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContext.PackageVersion;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode;
@ -426,8 +429,9 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
focus.getExpansion().addParameter().setName(p.getName()).setValue(p.getValue());
}
if (source.hasCompose())
handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension());
if (source.hasCompose()) {
handleCompose(source.getCompose(), focus.getExpansion(), expParams, source.getUrl(), focus.getExpansion().getExtension(), source);
}
if (canBeHeirarchy) {
for (ValueSetExpansionContainsComponent c : roots) {
@ -467,7 +471,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
return null;
}
private void handleCompose(ValueSetComposeComponent compose, ValueSetExpansionComponent exp, Parameters expParams, String ctxt, List<Extension> extensions)
private void handleCompose(ValueSetComposeComponent compose, ValueSetExpansionComponent exp, Parameters expParams, String ctxt, List<Extension> extensions, ValueSet valueSet)
throws ETooCostly, FileNotFoundException, IOException, FHIRException {
compose.checkNoModifiers("ValueSet.compose", "expanding");
// Exclude comes first because we build up a map of things to exclude
@ -481,11 +485,11 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
first = false;
else
canBeHeirarchy = false;
includeCodes(inc, exp, expParams, canBeHeirarchy, compose.hasInactive() && !compose.getInactive(), extensions);
includeCodes(inc, exp, expParams, canBeHeirarchy, compose.hasInactive() && !compose.getInactive(), extensions, valueSet);
}
}
private ValueSet importValueSet(String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError {
private ValueSet importValueSet(String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError {
if (value == null)
throw fail("unable to find value set with no identity");
ValueSet vs = context.fetchResource(ValueSet.class, value);
@ -521,10 +525,22 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
if (!existsInParams(exp.getParameter(), p.getName(), p.getValue()))
exp.getParameter().add(p);
}
copyExpansion(vso.getValueset().getExpansion().getContains());
if (isValueSetUnionImports(valueSet)) {
copyExpansion(vso.getValueset().getExpansion().getContains());
}
canBeHeirarchy = false; // if we're importing a value set, we have to be combining, so we won't try for a heirarchy
return vso.getValueset();
}
protected boolean isValueSetUnionImports(ValueSet valueSet) {
PackageVersion p = (PackageVersion) valueSet.getUserData("package");
if (p != null) {
return p.getDate().before(new GregorianCalendar(2022, Calendar.MARCH, 31).getTime());
} else {
return false;
}
}
public void copyExpansion(List<ValueSetExpansionContainsComponent> list) {
for (ValueSetExpansionContainsComponent cc : list) {
@ -562,11 +578,11 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
}
}
private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List<Extension> extensions) throws ETooCostly, FileNotFoundException, IOException, FHIRException {
private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List<Extension> extensions, ValueSet valueSet) throws ETooCostly, FileNotFoundException, IOException, FHIRException {
inc.checkNoModifiers("Compose.include", "expanding");
List<ValueSet> imports = new ArrayList<ValueSet>();
for (UriType imp : inc.getValueSet()) {
imports.add(importValueSet(imp.getValue(), exp, expParams, noInactive));
imports.add(importValueSet(imp.getValue(), exp, expParams, noInactive, valueSet));
}
if (!inc.hasSystem()) {

View File

@ -1,5 +1,7 @@
package org.hl7.fhir.r5.test;
import java.util.Date;
import org.hl7.fhir.r5.context.CanonicalResourceManager;
import org.hl7.fhir.r5.context.CanonicalResourceManager.CanonicalResourceProxy;
import org.hl7.fhir.r5.context.IWorkerContext.PackageVersion;
@ -425,14 +427,13 @@ public class CanonicalResourceManagerTests {
vs2.setUrl("http://terminology.hl7.org/ValueSet/234");
vs2.setVersion("2000.0.0");
vs2.setName("2");
mrm.see(vs1, new PackageVersion("hl7.fhir.r4.core", "4.0.1"));
mrm.see(vs1, new PackageVersion("hl7.fhir.r4.core", "4.0.1", new Date()));
Assertions.assertNotNull(mrm.get("http://terminology.hl7.org/ValueSet/234"));
Assertions.assertNotNull(mrm.get("http://terminology.hl7.org/ValueSet/234", "2.0.0"));
Assertions.assertTrue(mrm.get("http://terminology.hl7.org/ValueSet/234").getName().equals("1"));
mrm.see(vs2, new PackageVersion("hl7.terminology.r4", "4.0.1"));
mrm.see(vs2, new PackageVersion("hl7.terminology.r4", "4.0.1", new Date()));
Assertions.assertNotNull(mrm.get("http://terminology.hl7.org/ValueSet/234"));
Assertions.assertTrue(mrm.get("http://terminology.hl7.org/ValueSet/234").getName().equals("2"));
Assertions.assertNull(mrm.get("http://terminology.hl7.org/ValueSet/234", "2.0.0")); // this will get dropped completely because of UTG rules

View File

@ -622,6 +622,7 @@ public class I18nConstants {
public static final String VALUESET_REFERENCE_UNKNOWN = "VALUESET_REFERENCE_UNKNOWN";
public static final String VALUESET_UNC_SYSTEM_WARNING = "VALUESET_UNC_SYSTEM_WARNING";
public static final String VALUESET_UNC_SYSTEM_WARNING_VER = "VALUESET_UNC_SYSTEM_WARNING_VER";
public static final String VALUESET_IMPORT_UNION_INTERSECTION = "VALUESET_IMPORT_UNION_INTERSECTION";
public static final String VERSION_MISMATCH_THE_CONTEXT_HAS_VERSION__LOADED_AND_THE_NEW_CONTENT_BEING_LOADED_IS_VERSION_ = "Version_mismatch_The_context_has_version__loaded_and_the_new_content_being_loaded_is_version_";
public static final String WRONG_NAMESPACE__EXPECTED_ = "Wrong_namespace__expected_";
public static final String WRONG_TYPE_FOR_RESOURCE = "Wrong_type_for_resource";

View File

@ -42,9 +42,12 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -1134,6 +1137,28 @@ public class NpmPackage {
public InputStream load(PackageResourceInformation p) throws FileNotFoundException {
return new FileInputStream(p.filename);
}
public Date dateAsDate() {
try {
String d = date();
if (d == null) {
switch (name()) {
case "hl7.fhir.r2.core": d = "20151024000000"; break;
case "hl7.fhir.r2b.core": d = "20160330000000"; break;
case "hl7.fhir.r3.core": d = "20191024000000"; break;
case "hl7.fhir.r4.core": d = "20191030000000"; break;
case "hl7.fhir.r4b.core": d = "202112200000000"; break;
case "hl7.fhir.r5.core": d = "20211219000000"; break;
default:
return new Date();
}
}
return new SimpleDateFormat("yyyyMMddHHmmss").parse(d);
} catch (ParseException e) {
// this really really shouldn't happen
return new Date();
}
}
}

View File

@ -706,3 +706,5 @@ TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG_UCUM = The value in the instance ({0
TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR = Base64 encoded values are not allowed to contain any whitespace (per RFC 4648). Note that non-validating readers are encouraged to accept whitespace anyway
TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING = Base64 encoded values SHOULD not contain any whitespace (per RFC 4648). Note that non-validating readers are encouraged to accept whitespace anyway
SD_DERIVATION_KIND_MISMATCH = The structure definition constrains a kind of {0}, but has a different kind ({1})
VALUESET_IMPORT_UNION_INTERSECTION = This value set has an include with multiple imported value sets. Per issue https://jira.hl7.org/browse/FHIR-25179, there has been confusion in the past whether these value sets are unioned or intersectioned. If this value set is contained in a package published prior to March 31 2022, it will be treated as a union, otherwise it will be treated as an intersection

View File

@ -280,7 +280,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
if (userAgent != null) {
contextBuilder.withUserAgent(userAgent);
}
context = contextBuilder.fromDefinitions(source, ValidatorUtils.loaderForVersion(version), new PackageVersion(src));
context = contextBuilder.fromDefinitions(source, ValidatorUtils.loaderForVersion(version), new PackageVersion(src, new Date()));
ValidatorUtils.grabNatives(getBinaries(), source, "http://hl7.org/fhir");
}
// ucum-essence.xml should be in the class path. if it's not, ask about how to sort this out

View File

@ -5757,6 +5757,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return "htmlChecks2()";
}
// clarification in FHIRPath spec
if (expr.equals("name.matches('[A-Z]([A-Za-z0-9_]){0,254}')")) {
return "name.matches('^[A-Z]([A-Za-z0-9_]){0,254}$')";
}
if ("eld-19".equals(key)) {
return "path.matches('^[^\\\\s\\\\.,:;\\\\\\'\"\\\\/|?!@#$%&*()\\\\[\\\\]{}]{1,64}(\\\\.[^\\\\s\\\\.,:;\\\\\\'\"\\\\/|?!@#$%&*()\\\\[\\\\]{}]{1,64}(\\\\[x\\\\])?(\\\\:[^\\\\s\\\\.]+)?)*$')";
}
if ("eld-20".equals(key)) {
return "path.matches('^[A-Za-z][A-Za-z0-9]*(\\\\.[a-z][A-Za-z0-9]*(\\\\[x])?)*$')";
}
// handled in 4.0.1
if ("(component.empty() and hasMember.empty()) implies (dataAbsentReason or value)".equals(expr)) {
return "(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())";

View File

@ -98,6 +98,9 @@ public class ValueSetValidator extends BaseValidator {
}
i++;
}
if (valuesets.size() > 1) {
warning(errors, IssueType.INFORMATIONAL, stack.getLiteralPath(), false, I18nConstants.VALUESET_IMPORT_UNION_INTERSECTION);
}
List<Element> concepts = include.getChildrenByName("concept");
List<Element> filters = include.getChildrenByName("filter");
if (!Utilities.noString(system)) {

View File

@ -0,0 +1,12 @@
-------------------------------------------------------------------------------------
{"code" : {
"system" : "http://www.ada.org/snodent",
"code" : "210965D",
"display" : "Anterior part of lower alveolar ridge"
}, "valueSet" :null, "lang":"null", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "versionFlexible":"false"}####
v: {
"display" : "Anterior part of lower alveolar ridge",
"code" : "210965D",
"system" : "http://www.ada.org/snodent"
}
-------------------------------------------------------------------------------------