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:
parent
e8aab6db0e
commit
cf95c1a2ba
|
@ -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<>();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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())";
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
-------------------------------------------------------------------------------------
|
Loading…
Reference in New Issue