Merge pull request #1379 from hapifhir/2023-08-gg-terminology-status

warnings for status on terminology resources
This commit is contained in:
Grahame Grieve 2023-08-02 23:57:42 +10:00 committed by GitHub
commit 83bbd09627
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 331 additions and 100 deletions

View File

@ -2,8 +2,11 @@
* Fix invalid integer detection
* Improved invariant checking
* Create warnings for status on terminology resources
## Other code changes
* Update obligation handling code for split definitions
* Update ICF importer to handle grouping levels
* fix up copy directory for case differences

View File

@ -1490,6 +1490,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
String system = null;
String code = null;
String version = null;
List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
TerminologyServiceErrorClass err = TerminologyServiceErrorClass.UNKNOWN;
for (ParametersParameterComponent p : pOut.getParameter()) {
if (p.hasValue()) {
@ -1507,6 +1509,18 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
code = ((PrimitiveType<?>) p.getValue()).asStringValue();
} else if (p.getName().equals("x-caused-by-unknown-system")) {
err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED;
} else if (p.getName().equals("warning-withdrawn")) {
OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXPIRED);
iss.getDetails().setText(formatMessage(I18nConstants.MSG_WITHDRAWN, ((PrimitiveType<?>) p.getValue()).asStringValue()));
issues.add(iss);
} else if (p.getName().equals("warning-deprecated")) {
OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXPIRED);
iss.getDetails().setText(formatMessage(I18nConstants.MSG_DEPRECATED, ((PrimitiveType<?>) p.getValue()).asStringValue()));
issues.add(iss);
} else if (p.getName().equals("warning-retired")) {
OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXPIRED);
iss.getDetails().setText(formatMessage(I18nConstants.MSG_RETIRED, ((PrimitiveType<?>) p.getValue()).asStringValue()));
issues.add(iss);
} else if (p.getName().equals("cause")) {
try {
IssueType it = IssueType.fromCode(((StringType) p.getValue()).getValue());
@ -1524,15 +1538,18 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
}
}
}
ValidationResult res = null;
if (!ok) {
return new ValidationResult(IssueSeverity.ERROR, message+" (from "+tcc.getClient().getId()+")", err, null).setTxLink(txLog.getLastId());
res = new ValidationResult(IssueSeverity.ERROR, message+" (from "+tcc.getClient().getId()+")", err, null).setTxLink(txLog.getLastId());
} else if (message != null && !message.equals("No Message returned")) {
return new ValidationResult(IssueSeverity.WARNING, message+" (from "+tcc.getClient().getId()+")", system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display, null).setTxLink(txLog.getLastId());
res = new ValidationResult(IssueSeverity.WARNING, message+" (from "+tcc.getClient().getId()+")", system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display, null).setTxLink(txLog.getLastId());
} else if (display != null) {
return new ValidationResult(system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display).setTxLink(txLog.getLastId());
res = new ValidationResult(system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display).setTxLink(txLog.getLastId());
} else {
return new ValidationResult(system, version, new ConceptDefinitionComponent().setCode(code), null).setTxLink(txLog.getLastId());
res = new ValidationResult(system, version, new ConceptDefinitionComponent().setCode(code), null).setTxLink(txLog.getLastId());
}
res.setIssues(issues );
return res;
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -315,6 +315,14 @@ public interface IWorkerContext {
}
}
public void setIssues(List<OperationOutcomeIssueComponent> issues) {
if (this.issues != null) {
issues.addAll(this.issues);
}
this.issues = issues;
}
}
public class CodingValidationRequest {

View File

@ -1755,6 +1755,14 @@ public String toString() {
return false;
}
public boolean hasParameterValue(String name, String value) {
for (ParametersParameterComponent p : getParameter()) {
if (p.getName().equals(name) && p.hasValue() && value.equals(p.getValue().primitiveValue()))
return true;
}
return false;
}
public boolean hasParameter(String name) {
for (ParametersParameterComponent p : getParameter()) {
if (p.getName().equals(name))

View File

@ -2839,9 +2839,21 @@ public class ValueSet extends MetadataResource {
, total, offset, parameter, property, contains);
}
public boolean hasParameterValue(String name, String value) {
for (ValueSetExpansionParameterComponent p : getParameter()) {
if (name.equals(p.getName()) && p.hasValue() && value.equals(p.getValue().primitiveValue())) {
return true;
}
}
return false;
}
public void addParameter(String name, DataType value) {
getParameter().add(new ValueSetExpansionParameterComponent(name).setValue(value));
}
public String fhirType() {
return "ValueSet.expansion";
}
}

View File

@ -124,7 +124,6 @@ public class ValueSetExpander extends ValueSetProcessBase {
private static final boolean REPORT_VERSION_ANYWAY = true;
private IWorkerContext context;
private ValueSet focus;
private List<String> allErrors = new ArrayList<>();
private List<String> requiredSupplements = new ArrayList<>();
@ -151,9 +150,9 @@ public class ValueSetExpander extends ValueSetProcessBase {
private ValueSetExpansionContainsComponent addCode(WorkingContext wc, 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<Extension> csExtList, List<Extension> vsExtList) throws ETooCostly {
List<ConceptPropertyComponent> csProps, List<org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent> expProps, List<Extension> csExtList, List<Extension> vsExtList, ValueSetExpansionComponent exp) throws ETooCostly {
if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code))
if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code, exp))
return null;
if (noInactive && inactive) {
return null;
@ -285,10 +284,12 @@ public class ValueSetExpander extends ValueSetProcessBase {
return n;
}
private boolean filterContainsCode(List<ValueSet> filters, String system, String code) {
for (ValueSet vse : filters)
private boolean filterContainsCode(List<ValueSet> filters, String system, String code, ValueSetExpansionComponent exp) {
for (ValueSet vse : filters) {
checkCanonical(exp, vse);
if (expansionContainsCode(vse.getExpansion().getContains(), system, code))
return true;
}
return false;
}
@ -312,18 +313,18 @@ public class ValueSetExpander extends ValueSetProcessBase {
return null;
}
private void addCodeAndDescendents(WorkingContext wc, ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc) throws FHIRException, ETooCostly {
private void addCodeAndDescendents(WorkingContext wc, ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly {
focus.checkNoModifiers("Expansion.contains", "expanding");
ValueSetExpansionContainsComponent np = null;
for (String code : getCodesForConcept(focus, expParams)) {
ValueSetExpansionContainsComponent t = addCode(wc, focus.getSystem(), code, 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(), null, focus.getExtension());
convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), focus.getExtensionString(ToolingExtensions.EXT_DEFINITION), filters, noInactive, false, vsProps, null, focus.getProperty(), null, focus.getExtension(), exp);
if (np == null) {
np = t;
}
}
for (ValueSetExpansionContainsComponent c : focus.getContains())
addCodeAndDescendents(wc, c, np, expParams, filters, noInactive, vsProps, vsSrc);
addCodeAndDescendents(wc, c, np, expParams, filters, noInactive, vsProps, vsSrc, exp);
}
private List<String> getCodesForConcept(ValueSetExpansionContainsComponent focus, Parameters expParams) {
@ -349,8 +350,8 @@ public class ValueSetExpander extends ValueSetProcessBase {
return list;
}
private void addCodeAndDescendents(WorkingContext wc,CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters,
ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, List<WorkingContext> otherFilters) throws FHIRException, ETooCostly {
private void addCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filters,
ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, List<WorkingContext> otherFilters, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly {
def.checkNoModifiers("Code in Code System", "expanding");
if (exclusion != null) {
if (exclusion.getCode().equals(def.getCode()))
@ -362,19 +363,19 @@ public class ValueSetExpander extends ValueSetProcessBase {
boolean dep = CodeSystemUtilities.isDeprecated(cs, def, false);
if ((includeAbstract || !abs) && filterFunc.includeConcept(cs, def) && passesOtherFilters(otherFilters, cs, def.getCode())) {
for (String code : getCodesForConcept(def, expParams)) {
ValueSetExpansionContainsComponent t = addCode(wc, system, code, def.getDisplay(), cs.getLanguage(), parent, def.getDesignation(), expParams, abs, inc, def.getDefinition(), filters, noInactive, dep, vsProps, def.getProperty(), null, def.getExtension(), null);
ValueSetExpansionContainsComponent t = addCode(wc, system, code, def.getDisplay(), cs.getLanguage(), parent, def.getDesignation(), expParams, abs, inc, def.getDefinition(), filters, noInactive, dep, vsProps, def.getProperty(), null, def.getExtension(), null, exp);
if (np == null) {
np = t;
}
}
}
for (ConceptDefinitionComponent c : def.getConcept()) {
addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters);
addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp);
}
if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
for (ConceptDefinitionComponent c : children)
addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters);
addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp);
}
}
@ -401,7 +402,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc) throws ETooCostly, FHIRException {
private void addCodes(ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params, Parameters expParams, List<ValueSet> filters, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws ETooCostly, FHIRException {
if (expand != null) {
if (expand.getContains().size() > maxExpansionSize)
throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, vsSrc.getUrl(), ">" + Integer.toString(expand.getContains().size())));
@ -410,7 +411,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
params.add(p);
}
copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps, vsSrc);
copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps, vsSrc, exp);
}
}
@ -450,8 +451,6 @@ public class ValueSetExpander extends ValueSetProcessBase {
throw fail("not done yet - multiple filters");
}
private void excludeCodes(WorkingContext wc, ValueSetExpansionComponent expand, List<ValueSetExpansionParameterComponent> params) {
for (ValueSetExpansionContainsComponent c : expand.getContains()) {
excludeCode(wc, c.getSystem(), c.getCode());
@ -460,9 +459,10 @@ public class ValueSetExpander extends ValueSetProcessBase {
private boolean existsInParams(List<ValueSetExpansionParameterComponent> params, String name, DataType value) {
for (ValueSetExpansionParameterComponent p : params) {
if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false))
if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) {
return true;
}
}
return false;
}
@ -503,6 +503,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
focus.setExpansion(new ValueSet.ValueSetExpansionComponent());
focus.getExpansion().setTimestampElement(DateTimeType.now());
focus.getExpansion().setIdentifier(Factory.createUUID());
checkCanonical(focus.getExpansion(), focus);
for (ParametersParameterComponent p : expParams.getParameter()) {
if (Utilities.existsInList(p.getName(), "includeDesignations", "excludeNested", "activeOnly", "offset", "count")) {
focus.getExpansion().addParameter().setName(p.getName()).setValue(p.getValue());
@ -642,6 +643,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
throw fail("Unable to find imported value set " + value);
}
}
checkCanonical(exp, vs);
if (noInactive) {
expParams = expParams.copy();
expParams.addParameter("activeOnly", true);
@ -714,12 +716,12 @@ public class ValueSetExpander extends ValueSetProcessBase {
}
}
private void copyImportContains(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filter, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc) throws FHIRException, ETooCostly {
private void copyImportContains(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent parent, Parameters expParams, List<ValueSet> filter, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly {
for (ValueSetExpansionContainsComponent c : list) {
c.checkNoModifiers("Imported Expansion in Code System", "expanding");
ValueSetExpansionContainsComponent np = addCode(dwc, 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(), null, c.getExtension());
copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc);
filter, noInactive, false, vsProps, null, c.getProperty(), null, c.getExtension(), exp);
copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc, exp);
}
}
@ -734,9 +736,10 @@ public class ValueSetExpander extends ValueSetProcessBase {
if (imports.isEmpty()) // though this is not supposed to be the case
return;
ValueSet base = imports.get(0);
checkCanonical(exp, base);
imports.remove(0);
base.checkNoModifiers("Imported ValueSet", "expanding");
copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base);
copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base, exp);
} else {
CodeSystem cs = context.fetchSupplementedCodeSystem(inc.getSystem());
if (ValueSetUtilities.isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) {
@ -783,7 +786,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
}
}
for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) {
addCodeAndDescendents(dwc, cc, null, expParams, imports, noInactive, vsProps, vs);
addCodeAndDescendents(dwc, cc, null, expParams, imports, noInactive, vsProps, vs, exp);
}
}
@ -795,6 +798,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
else
throw failTSE("Unable to find code system " + inc.getSystem().toString());
}
checkCanonical(exp, cs);
cs.checkNoModifiers("Code System", "expanding");
if (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT)
throw failTSE("Code system " + inc.getSystem().toString() + " is incomplete");
@ -813,7 +817,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
// special case - add all the code system
for (ConceptDefinitionComponent def : cs.getConcept()) {
addCodeAndDescendents(dwc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), null);
addCodeAndDescendents(dwc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), null, exp);
}
if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
addFragmentWarning(exp, cs);
@ -846,7 +850,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
isAbstract = CodeSystemUtilities.isNotSelectable(cs, def);
}
addCode(dwc, 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, isAbstract, inactive, def == null ? null : def.getDefinition(), imports, noInactive, false, exp.getProperty(), def != null ? def.getProperty() : null, null, def == null ? null : def.getExtension(), c.getExtension());
expParams, isAbstract, inactive, def == null ? null : def.getDefinition(), imports, noInactive, false, exp.getProperty(), def != null ? def.getProperty() : null, null, def == null ? null : def.getExtension(), c.getExtension(), exp);
}
}
if (inc.getFilter().size() > 0) {
@ -876,14 +880,14 @@ public class ValueSetExpander extends ValueSetProcessBase {
ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue());
if (def == null)
throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters);
addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
} else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISNOTA) {
// special: all codes in the target code system that are not under the value
ConceptDefinitionComponent defEx = getConceptForCode(cs.getConcept(), fc.getValue());
if (defEx == null)
throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
for (ConceptDefinitionComponent def : cs.getConcept()) {
addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, defEx, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters);
addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, defEx, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
}
} else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) {
// special: all codes in the target code system under the value
@ -891,11 +895,11 @@ public class ValueSetExpander extends ValueSetProcessBase {
if (def == null)
throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'");
for (ConceptDefinitionComponent c : def.getConcept())
addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters);
addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
for (ConceptDefinitionComponent c : children)
addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters);
addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp);
}
} else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) {
@ -907,18 +911,18 @@ public class ValueSetExpander extends ValueSetProcessBase {
if (def.getDisplay().contains(fc.getValue()) && passesOtherFilters(filters, cs, def.getCode())) {
for (String code : getCodesForConcept(def, expParams)) {
ValueSetExpansionContainsComponent t = addCode(wc, inc.getSystem(), code, 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.getExtension(), null);
def.getDefinition(), imports, noInactive, false, exp.getProperty(), def.getProperty(), null, def.getExtension(), null, exp);
}
}
}
}
} else if (isDefinedProperty(cs, fc.getProperty())) {
for (ConceptDefinitionComponent def : cs.getConcept()) {
addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new PropertyFilter(allErrors, fc, getPropertyDefinition(cs, fc.getProperty())), noInactive, exp.getProperty(), filters);
addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new PropertyFilter(allErrors, fc, getPropertyDefinition(cs, fc.getProperty())), noInactive, exp.getProperty(), filters, exp);
}
} else if ("code".equals(fc.getProperty()) && fc.getOp() == FilterOperator.REGEX) {
for (ConceptDefinitionComponent def : cs.getConcept()) {
addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new RegexFilter(allErrors, fc.getValue()), noInactive, exp.getProperty(), filters);
addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new RegexFilter(allErrors, fc.getValue()), noInactive, exp.getProperty(), filters, exp);
}
} else {
throw fail("Filter by property[" + fc.getProperty() + "] and op[" + fc.getOp() + "] is not supported yet");

View File

@ -1,20 +1,31 @@
package org.hl7.fhir.r5.terminologies.utilities;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r5.model.OperationOutcome.IssueType;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.StandardsStatus;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
public class ValueSetProcessBase {
protected IWorkerContext context;
public static class AlternateCodesProcessingRules {
private boolean all;
private List<String> uses = new ArrayList<>();
@ -77,6 +88,88 @@ public class ValueSetProcessBase {
}
}
protected List<OperationOutcomeIssueComponent> makeIssue(IssueSeverity level, IssueType type, String location, String message) {
OperationOutcomeIssueComponent result = new OperationOutcomeIssueComponent();
switch (level) {
case ERROR:
result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR);
break;
case FATAL:
result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.FATAL);
break;
case INFORMATION:
result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION);
break;
case WARNING:
result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING);
break;
}
result.setCode(type);
result.addLocation(location);
result.getDetails().setText(message);
ArrayList<OperationOutcomeIssueComponent> list = new ArrayList<>();
list.add(result);
return list;
}
public void checkCanonical(List<OperationOutcomeIssueComponent> issues, String path, CanonicalResource resource) {
if (resource != null) {
StandardsStatus standardsStatus = ToolingExtensions.getStandardsStatus(resource);
if (standardsStatus == StandardsStatus.DEPRECATED) {
addToIssues(issues, makeStatusIssue(path, "deprecated", I18nConstants.MSG_DEPRECATED, resource));
} else if (standardsStatus == StandardsStatus.WITHDRAWN) {
addToIssues(issues, makeStatusIssue(path, "withdrawn", I18nConstants.MSG_WITHDRAWN, resource));
} else if (resource.getStatus() == PublicationStatus.RETIRED) {
addToIssues(issues, makeStatusIssue(path, "retired", I18nConstants.MSG_RETIRED, resource));
}
}
}
private List<OperationOutcomeIssueComponent> makeStatusIssue(String path, String id, String msg, CanonicalResource resource) {
List<OperationOutcomeIssueComponent> iss = makeIssue(IssueSeverity.INFORMATION, IssueType.EXPIRED, path, context.formatMessage(msg, resource.getVersionedUrl()));
// this is a testing hack - see TerminologyServiceTests
iss.get(0).setUserData("status-msg-name", "warning-"+id);
iss.get(0).setUserData("status-msg-value", new UriType(resource.getVersionedUrl()));
return iss;
}
private void addToIssues(List<OperationOutcomeIssueComponent> issues, List<OperationOutcomeIssueComponent> toAdd) {
for (OperationOutcomeIssueComponent t : toAdd) {
boolean found = false;
for (OperationOutcomeIssueComponent i : issues) {
if (i.getSeverity() == t.getSeverity() && i.getCode() == t.getCode() && i.getDetails().getText().equals(t.getDetails().getText())) { // ignore location
found = true;
}
}
if (!found) {
issues.add(t);
}
}
}
public void checkCanonical(ValueSetExpansionComponent params, CanonicalResource resource) {
if (resource != null) {
StandardsStatus standardsStatus = ToolingExtensions.getStandardsStatus(resource);
if (standardsStatus == StandardsStatus.DEPRECATED) {
if (!params.hasParameterValue("warning-deprecated", resource.getVersionedUrl())) {
params.addParameter("warning-deprecated", new UriType(resource.getVersionedUrl()));
}
} else if (standardsStatus == StandardsStatus.WITHDRAWN) {
if (!params.hasParameterValue("warning-withdrawn", resource.getVersionedUrl())) {
params.addParameter("warning-withdrawn", new UriType(resource.getVersionedUrl()));
}
} else if (resource.getStatus() == PublicationStatus.RETIRED) {
if (!params.hasParameterValue("warning-retired", resource.getVersionedUrl())) {
params.addParameter("warning-retired", new UriType(resource.getVersionedUrl()));
}
}
}
}
protected AlternateCodesProcessingRules altCodeParams = new AlternateCodesProcessingRules(false);
protected AlternateCodesProcessingRules allAltCodes = new AlternateCodesProcessingRules(true);
}

View File

@ -97,7 +97,6 @@ import com.google.j2objc.annotations.ReflectionSupport.Level;
public class ValueSetValidator extends ValueSetProcessBase {
private ValueSet valueset;
private IWorkerContext context;
private Map<String, ValueSetValidator> inner = new HashMap<>();
private ValidationOptions options;
private ValidationContextCarrier localContext;
@ -229,7 +228,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
for (Coding c : code.getCoding()) {
b.append(c.getSystem()+(c.hasVersion() ? "|"+c.getVersion() : "")+"#"+c.getCode());
Boolean ok = codeInValueSet(c.getSystem(), c.getVersion(), c.getCode(), info);
Boolean ok = codeInValueSet(path, c.getSystem(), c.getVersion(), c.getCode(), info);
if (ok == null && result != null && result == false) {
result = null;
} else if (ok != null && ok) {
@ -338,30 +337,6 @@ public class ValueSetValidator extends ValueSetProcessBase {
return versionTest == null && VersionUtilities.versionsMatch(versionTest, versionActual);
}
private List<OperationOutcomeIssueComponent> makeIssue(IssueSeverity level, IssueType type, String location, String message) {
OperationOutcomeIssueComponent result = new OperationOutcomeIssueComponent();
switch (level) {
case ERROR:
result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR);
break;
case FATAL:
result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.FATAL);
break;
case INFORMATION:
result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION);
break;
case WARNING:
result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING);
break;
}
result.setCode(type);
result.addLocation(location);
result.getDetails().setText(message);
ArrayList<OperationOutcomeIssueComponent> list = new ArrayList<>();
list.add(result);
return list;
}
public ValidationResult validateCode(Coding code) throws FHIRException {
return validateCode("Coding", code);
}
@ -375,6 +350,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
boolean inInclude = false;
List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
VersionInfo vi = new VersionInfo(this);
checkCanonical(issues, path, valueset);
String system = code.hasSystem() ? code.getSystem() : getValueSetSystemOrNull();
if (options.getValueSetMode() != ValueSetMode.CHECK_MEMERSHIP_ONLY) {
@ -434,6 +410,8 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
}
}
} else {
checkCanonical(issues, path, cs);
}
if (cs != null && cs.hasSupplements()) {
String msg = context.formatMessage(I18nConstants.CODESYSTEM_CS_NO_SUPPLEMENT, cs.getUrl());
@ -462,6 +440,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
throw new FHIRException("Unable to evaluate based on empty code system");
}
res = validateCode(path, code, cs, null);
res.setIssues(issues);
} else if (cs == null && valueset.hasExpansion() && inExpansion) {
// we just take the value set as face value then
res = new ValidationResult(system, wv, new ConceptDefinitionComponent().setCode(code.getCode()).setDisplay(code.getDisplay()), code.getDisplay());
@ -486,7 +465,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
// then, if we have a value set, we check it's in the value set
if (valueset != null && options.getValueSetMode() != ValueSetMode.NO_MEMBERSHIP_CHECK) {
if ((res==null || res.isOk())) {
Boolean ok = codeInValueSet(system, wv, code.getCode(), info);
Boolean ok = codeInValueSet(path, system, wv, code.getCode(), info);
if (ok == null || !ok) {
if (res == null) {
res = new ValidationResult((IssueSeverity) null, null, info.getIssues());
@ -982,14 +961,11 @@ public class ValueSetValidator extends ValueSetProcessBase {
return true;
}
public Boolean codeInValueSet(String system, String version, String code, ValidationProcessInfo info) throws FHIRException {
return codeInValueSet("code", system, version, code, info);
}
public Boolean codeInValueSet(String path, String system, String version, String code, ValidationProcessInfo info) throws FHIRException {
if (valueset == null) {
return false;
}
checkCanonical(info.getIssues(), path, valueset);
Boolean result = false;
VersionInfo vi = new VersionInfo(this);
@ -1029,15 +1005,15 @@ public class ValueSetValidator extends ValueSetProcessBase {
if (isValueSetUnionImports()) {
ok = false;
for (UriType uri : vsi.getValueSet()) {
if (inImport(uri.getValue(), system, version, code)) {
if (inImport(path, uri.getValue(), system, version, code, info)) {
return true;
}
}
} else {
ok = inImport(vsi.getValueSet().get(0).getValue(), system, version, code);
ok = inImport(path, vsi.getValueSet().get(0).getValue(), system, version, code, info);
for (int i = 1; i < vsi.getValueSet().size(); i++) {
UriType uri = vsi.getValueSet().get(i);
ok = ok && inImport(uri.getValue(), system, version, code);
ok = ok && inImport(path, uri.getValue(), system, version, code, info);
}
}
}
@ -1097,6 +1073,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
return null;
}
} else {
checkCanonical(info.getIssues(), path, cs);
if (vsi.hasFilter()) {
ok = true;
for (ConceptSetFilterComponent f : vsi.getFilter()) {
@ -1231,12 +1208,12 @@ public class ValueSetValidator extends ValueSetProcessBase {
return vsc;
}
private boolean inImport(String uri, String system, String version, String code) throws FHIRException {
private boolean inImport(String path, String uri, String system, String version, String code, ValidationProcessInfo info) throws FHIRException {
ValueSetValidator vs = getVs(uri);
if (vs == null) {
return false;
} else {
Boolean ok = vs.codeInValueSet(system, version, code, null);
Boolean ok = vs.codeInValueSet(path, system, version, code, info);
return ok != null && ok;
}
}

View File

@ -45,7 +45,7 @@ public class CompareUtilities extends BaseTestingUtilities {
String result = compareXml(expected, actual);
if (result != null && SHOW_DIFF) {
String diff = getDiffTool();
if (new File(diff).exists() || Utilities.isToken(diff)) {
if (diff != null && new File(diff).exists() || Utilities.isToken(diff)) {
Runtime.getRuntime().exec(new String[]{diff, expected, actual});
}
}

View File

@ -65,10 +65,11 @@ public class CSFile extends File {
//attempt to open a file triggers a directory listing
if (exists())
{
if(!this.getCanonicalFile().getName().equals(this.getName()))
if(!this.getCanonicalFile().getName().equals(this.getName())) {
throw new Error("Case mismatch of file "+ pathname+": found "+this.getCanonicalFile().getName());
}
}
}
}

View File

@ -35,7 +35,7 @@ import org.hl7.fhir.exceptions.FHIRException;
public enum StandardsStatus {
EXTERNAL, INFORMATIVE, DRAFT, TRIAL_USE, DEPRECATED, NORMATIVE;
EXTERNAL, INFORMATIVE, DRAFT, TRIAL_USE, DEPRECATED, NORMATIVE, WITHDRAWN;
public String toDisplay() {
switch (this) {
@ -51,6 +51,8 @@ public enum StandardsStatus {
return "External";
case DEPRECATED:
return "Deprecated";
case WITHDRAWN:
return "Withdrawn";
}
return "?";
}
@ -69,6 +71,8 @@ public enum StandardsStatus {
return "deprecated";
case EXTERNAL:
return "external";
case WITHDRAWN:
return "Withdrawn";
}
return "?";
}
@ -93,6 +97,8 @@ public enum StandardsStatus {
return EXTERNAL;
if (value.equalsIgnoreCase("DEPRECATED"))
return DEPRECATED;
if (value.equalsIgnoreCase("WITHDRAWN"))
return WITHDRAWN;
throw new FHIRException("Incorrect Standards Status '"+value+"'");
}
@ -110,6 +116,8 @@ public enum StandardsStatus {
return "XD";
case EXTERNAL:
return "X";
case WITHDRAWN:
return "W";
}
return "?";
}
@ -125,6 +133,7 @@ public enum StandardsStatus {
case INFORMATIVE:
return "#ffffe6";
case DEPRECATED:
case WITHDRAWN:
return "#ffcccc";
case EXTERNAL:
return "#e6ffff";
@ -143,6 +152,7 @@ public enum StandardsStatus {
case INFORMATIVE:
return "#ffffec";
case DEPRECATED:
case WITHDRAWN:
return "#ffcccc";
case EXTERNAL:
return "#ecffff";
@ -159,6 +169,8 @@ public enum StandardsStatus {
return (tgtSS == NORMATIVE || tgtSS == EXTERNAL );
if (this == DEPRECATED)
return (tgtSS == DEPRECATED );
if (this == WITHDRAWN)
return (tgtSS == WITHDRAWN );
return false;
}

View File

@ -307,7 +307,18 @@ public class Utilities {
CSFile src = new CSFile(sourceFolder);
if (!src.exists())
throw new FHIRException("Folder " + sourceFolder + " not found");
File dst = new File(destFolder);
if(!dst.getCanonicalFile().getName().equals(dst.getName())) {
File tmp = new File(destFolder+System.currentTimeMillis());
if (!dst.renameTo(tmp)) {
throw new IOException("fixing case from "+dst.getCanonicalFile().getName()+" to "+tmp.getName()+" failed");
}
if (!tmp.renameTo(dst)) {
throw new IOException("fixing case from "+tmp.getCanonicalFile().getName()+" to "+dst.getName()+" failed");
}
} else if (!dst.exists()) {
createDirectory(destFolder);
}
String[] files = src.list();
for (String f : files) {
@ -318,7 +329,7 @@ public class Utilities {
} else {
if (notifier != null)
notifier.copyFile(sourceFolder + File.separator + f, destFolder + File.separator + f);
copyFile(new CSFile(sourceFolder + File.separator + f), new CSFile(destFolder + File.separator + f));
copyFile(new CSFile(sourceFolder + File.separator + f), new /*CS*/File(destFolder + File.separator + f)); // case doesn't have to match on the target
}
}
}
@ -358,6 +369,10 @@ public class Utilities {
createDirectory(destFile.getParent());
}
destFile.createNewFile();
} else if (!destFile.getCanonicalFile().getName().equals(destFile.getName())) {
// case mismatch
destFile.delete();
destFile.createNewFile();
}
FileInputStream source = null;

View File

@ -950,6 +950,9 @@ public class I18nConstants {
public static final String FHIRPATH_OFTYPE_IMPOSSIBLE = "FHIRPATH_OFTYPE_IMPOSSIBLE";
public static final String ED_SEARCH_EXPRESSION_ERROR = "ED_SEARCH_EXPRESSION_ERROR";
public static final String SD_EXTENSION_URL_MISMATCH = "SD_EXTENSION_URL_MISMATCH";
public static final String MSG_DEPRECATED = "MSG_DEPRECATED";
public static final String MSG_WITHDRAWN = "MSG_WITHDRAWN";
public static final String MSG_RETIRED = "MSG_RETIRED";

View File

@ -1266,6 +1266,11 @@ public class NpmPackage {
return Utilities.existsInList(npm.asString("type"), "fhir.core", "Core");
}
public boolean isCoreExamples() {
return name().startsWith("hl7.fhir.r") && name().endsWith(".examples");
}
public boolean isTx() {
return npm.asString("name").startsWith("hl7.terminology");
}

View File

@ -1006,3 +1006,6 @@ FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT = The right side is inherently a coll
FHIRPATH_OFTYPE_IMPOSSIBLE = The type specified in ofType is {1} which is not a possible candidate for the existing types ({0}) in the expression {2}. Check the paths and types to be sure this is what is intended
ED_SEARCH_EXPRESSION_ERROR = Error in search expression ''{0}'': {1}
SD_EXTENSION_URL_MISMATCH = The fixed value for the extension URL is {1} which doesn''t match the canonical URL {0}
MSG_DEPRECATED = Reference to deprecated item {0}
MSG_WITHDRAWN = Reference to withdrawn item {0}
MSG_RETIRED = Reference to retired item {0}

View File

@ -423,4 +423,45 @@ class UtilitiesTest {
Assertions.assertFalse("\u0009\n\u000B\u000C\r\u0020\u0085\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000".matches("^.+$"));
}
@Test
@DisplayName("directory copy case tests")
void testFDirectoryCopy() throws IOException {
String src = Utilities.path("[tmp]", "test", "copy-source");
String dst = Utilities.path("[tmp]", "test", "copy-dest");
makeDir (src);
makeFile(Utilities.path(src, "Test.txt"), "source1");
makeDir (Utilities.path(src, "SUB"));
makeFile(Utilities.path(src, "SUB", "TEST.txt"), "source2");
makeDir (dst);
makeFile(Utilities.path(dst, "test.txt"), "dest1");
makeDir (Utilities.path(dst, "sub"));
makeFile(Utilities.path(dst, "sub", "test.txt"), "dest2");
Utilities.copyDirectory(src, dst, null);
checkDir (dst);
checkFile(Utilities.path(dst, "Test.txt"), "source1");
checkDir (Utilities.path(dst, "SUB"));
checkFile(Utilities.path(dst, "SUB", "TEST.txt"), "source2");
}
private void checkFile(String path, String content) throws IOException {
Assertions.assertTrue(new CSFile(path).exists());
Assertions.assertEquals(content, TextFile.fileToString(path));
}
private void checkDir(String path) throws IOException {
Assertions.assertTrue(new CSFile(path).exists());
}
private void makeFile(String path, String content) throws IOException {
TextFile.stringToFile(content, path);
}
private void makeDir(String path) throws IOException {
Utilities.createDirectory(path);
Utilities.clearDirectory(path);
}
}

View File

@ -520,13 +520,15 @@ public class IgLoader implements IValidationEngineLoader {
}
}
if (!pi.isCoreExamples()) {
if (loadInContext) {
// getContext().getLoadedPackages().add(pi.name() + "#" + pi.version());
// getContext().getLoadedPackages().add(pi.name() + "#" + pi.version());
getContext().loadFromPackage(pi, ValidatorUtils.loaderForVersion(pi.fhirVersion()));
}
for (String s : pi.listResources("CodeSystem", "ConceptMap", "ImplementationGuide", "CapabilityStatement", "SearchParameter", "Conformance", "StructureMap", "ValueSet", "StructureDefinition")) {
res.put(s, TextFile.streamToBytes(pi.load("package", s)));
}
}
String ini = "[FHIR]\r\nversion=" + pi.fhirVersion() + "\r\n";
res.put("version.info", ini.getBytes());
return res;

View File

@ -3,7 +3,11 @@ package org.hl7.fhir.validation.special;
import java.util.Collections;
import java.util.Comparator;
import org.hl7.fhir.ParametersParameter;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r5.model.ValueSet;
@ -16,8 +20,14 @@ import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
public class TxTesterSorters {
public static void sortParameters(Parameters po) {
Collections.sort(po.getParameter(), new TxTesterSorters.ParameterSorter());
for (ParametersParameterComponent p : po.getParameter()) {
if (p.getResource() != null && p.getResource() instanceof OperationOutcome) {
Collections.sort(((OperationOutcome) p.getResource()).getIssue(), new TxTesterSorters.OperationIssueSorter());
}
}
}
@ -44,6 +54,23 @@ public class TxTesterSorters {
public static class OperationIssueSorter implements Comparator<OperationOutcomeIssueComponent> {
@Override
public int compare(OperationOutcomeIssueComponent o1, OperationOutcomeIssueComponent o2) {
if (o1.hasSeverity() && o2.hasSeverity() && o1.getSeverity() != o2.getSeverity()) {
return o1.getSeverity().compareTo(o2.getSeverity());
}
if (o1.hasCode() && o2.hasCode() && o1.getCode() != o2.getCode()) {
return o1.getCode().compareTo(o2.getCode());
}
if (o1.getDetails().hasText() && o2.getDetails().hasText() && !o1.getDetails().getText().equals(o2.getDetails().getText())) {
return o1.getDetails().getText().compareTo(o2.getDetails().getText());
}
return 0;
}
}
public static class DesignationSorter implements Comparator<ConceptReferenceDesignationComponent> {
@Override