further modularise validator and add Measure Validation

This commit is contained in:
Grahame Grieve 2020-04-02 11:05:55 +11:00
parent 95c2f99e7b
commit ab581fdf86
14 changed files with 1579 additions and 1047 deletions

View File

@ -125,6 +125,9 @@ public class ObjectConverter {
}
public static CodeableConcept readAsCodeableConcept(Element element) {
if (element == null) {
return null;
}
CodeableConcept cc = new CodeableConcept();
List<Element> list = new ArrayList<Element>();
element.getNamedChildren("coding", list);

View File

@ -342,6 +342,15 @@ public class CodeableConcept extends DataType implements ICompositeType {
super();
addCoding(code);
}
public boolean matches(CodeableConcept other) {
for (Coding c : other.getCoding()) {
if (hasCoding(c.getSystem(), c.getCode())) {
return true;
}
}
return false;
}

View File

@ -164,6 +164,7 @@ public class I18nConstants {
public final static String TERMINOLOGY_TX_SYSTEM_INVALID = "Terminology_TX_System_Invalid";
public final static String TERMINOLOGY_TX_SYSTEM_NOTKNOWN = "Terminology_TX_System_NotKnown";
public final static String TERMINOLOGY_TX_SYSTEM_RELATIVE = "Terminology_TX_System_Relative";
public final static String TERMINOLOGY_TX_SYSTEM_NO_CODE = "TERMINOLOGY_TX_SYSTEM_NO_CODE";
public final static String TERMINOLOGY_TX_SYSTEM_UNKNOWN = "Terminology_TX_System_Unknown";
public final static String TERMINOLOGY_TX_SYSTEM_VALUESET = "Terminology_TX_System_ValueSet";
public final static String TERMINOLOGY_TX_SYSTEM_VALUESET2 = "Terminology_TX_System_ValueSet2";
@ -437,5 +438,28 @@ public class I18nConstants {
public final static String XML_ATTR_VALUE_INVALID = "xml_attr_value_invalid";
public final static String XML_ENCODING_INVALID = "xml_encoding_invalid";
public final static String XML_STATED_ENCODING_INVALID = "xml_stated_encoding_invalid";
}
public static final String MEASURE_MR_GRP_NO_CODE = "MEASURE_MR_GRP_NO_CODE";
public static final String MEASURE_MR_GRP_UNK_CODE = "MEASURE_MR_GRP_UNK_CODE";
public static final String MEASURE_MR_GRP_DUPL_CODE = "MEASURE_MR_GRP_DUPL_CODE";
public static final String MEASURE_MR_GRP_MISSING_BY_CODE = "MEASURE_MR_GRP_MISSING_BY_CODE";
public static final String MEASURE_MR_GRP_POP_NO_CODE = "MEASURE_MR_GRP_POP_NO_CODE";
public static final String MEASURE_MR_GRP_POP_UNK_CODE = "MEASURE_MR_GRP_POP_UNK_CODE";
public static final String MEASURE_MR_GRP_POP_DUPL_CODE = "MEASURE_MR_GRP_POP_DUPL_CODE";
public static final String MEASURE_MR_GRP_POP_MISSING_BY_CODE = "MEASURE_MR_GRP_POP_MISSING_BY_CODE";
public static final String MEASURE_MR_GRP_NO_USABLE_CODE = "MEASURE_MR_GRP_NO_USABLE_CODE";
public static final String MEASURE_MR_GRP_NO_WRONG_CODE = "MEASURE_MR_GRP_NO_WRONG_CODE";
public static final String DUPLICATE_ID = "DUPLICATE_ID";
public static final String MEASURE_MR_GRP_POP_COUNT_MISMATCH = "MEASURE_MR_GRP_POP_COUNT_MISMATCH";
public static final String MEASURE_MR_GRP_POP_NO_SUBJECTS = "MEASURE_MR_GRP_POP_NO_SUBJECTS";
public static final String MEASURE_MR_GRP_POP_NO_COUNT = "MEASURE_MR_GRP_POP_NO_COUNT";
public static final String MEASURE_M_GROUP_CODE = "MEASURE_M_GROUP_CODE";
public static final String MEASURE_M_GROUP_POP_NO_CODE = "MEASURE_M_GROUP_POP_NO_CODE";
public static final String MEASURE_M_GROUP_STRATA_NO_CODE = "MEASURE_M_GROUP_STRATA_NO_CODE";
public static final String MEASURE_M_GROUP_STRATA_COMP_NO_CODE = "MEASURE_M_GROUP_STRATA_COMP_NO_CODE";
public static final String MEASURE_M_NO_GROUPS = "MEASURE_M_NO_GROUPS";
public static final String MEASURE_M_GROUP_POP = "MEASURE_M_GROUP_POP";
public static final String MEASURE_M_GROUP_STRATA = "MEASURE_M_GROUP_STRATA";
public static final String MEASURE_M_LIB_UNKNOWN = "MEASURE_M_LIB_UNKNOWN";
public static final String TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE = "TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE";
}

View File

@ -436,4 +436,28 @@ documentmsg = (document)
xml_attr_value_invalid = The XML Attribute {0} has an illegal character
xml_encoding_invalid = The XML encoding is invalid (must be UTF-8)
xml_stated_encoding_invalid = The XML encoding stated in the header is invalid (must be "UTF-8" if stated)
XHTML_URL_INVALID = The URL {0} is not valid
XHTML_URL_INVALID = The URL {0} is not valid
MEASURE_MR_GRP_NO_CODE = Group should have a code that matches the group definition in the measure
MEASURE_MR_GRP_UNK_CODE = The code for this group has no match in the measure definition
MEASURE_MR_GRP_DUPL_CODE = The code for this group is duplicated with another group
MEASURE_MR_GRP_MISSING_BY_CODE = The MeasureReport does not include a group for the group {0}
MEASURE_MR_GRP_NO_USABLE_CODE = None of the codes provided are usable for comparison - need both system and code on at least one code
MEASURE_MR_GRP_NO_WRONG_CODE = The code provided ({0}) does not match the code specified in the measure report ({1})
DUPLICATE_ID = Duplicate id value ''{0}''
TERMINOLOGY_TX_SYSTEM_NO_CODE = A code with no system has no defined meaning. A system should be provided
MEASURE_MR_GRP_POP_NO_CODE = Group should have a code that matches the group population definition in the measure
MEASURE_MR_GRP_POP_UNK_CODE = The code for this group population has no match in the measure definition
MEASURE_MR_GRP_POP_DUPL_CODE = The code for this group population is duplicated with another group
MEASURE_MR_GRP_POP_MISSING_BY_CODE = The MeasureReport does not include a population group for the population group {0}
MEASURE_MR_GRP_POP_COUNT_MISMATCH = Mismatch between count {0} and number of subjects {1}
MEASURE_MR_GRP_POP_NO_SUBJECTS = Reports where type is not ''subject-list'' don't have subjects listed
MEASURE_MR_GRP_POP_NO_COUNT = Count should be present for reports where type is not ''subject-list''
MEASURE_M_NO_GROUPS = A measure should contain at least one group
MEASURE_M_GROUP_CODE = Groups should have codes when there is more than one group
MEASURE_M_GROUP_POP = Measure Groups should have at least one population
MEASURE_M_GROUP_STRATA = Measure Groups should have at least one stratifier
MEASURE_M_GROUP_POP_NO_CODE = A measure group population should have a code when there is more than one population
MEASURE_M_GROUP_STRATA_NO_CODE = A measure group stratifier should have a code when there is more than one population
MEASURE_M_GROUP_STRATA_COMP_NO_CODE = A measure group stratifier component should have a code when there is more than one population
MEASURE_M_LIB_UNKNOWN = The Library {0} could not be resolved, so expression validation may not be correct
TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE = Canonical URLs must be absolute URLs if they are not fragment references ({0})

View File

@ -1,5 +1,7 @@
package org.hl7.fhir.validation;
import static org.apache.commons.lang3.StringUtils.isBlank;
/*-
* #%L
* org.hl7.fhir.validation
@ -52,6 +54,11 @@ POSSIBILITY OF SUCH DAMAGE.
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
@ -61,6 +68,7 @@ public class BaseValidator {
protected Source source;
protected IWorkerContext context;
protected TimeTracker timeTracker = new TimeTracker();
public BaseValidator(IWorkerContext context){
@ -545,4 +553,58 @@ public class BaseValidator {
}
return thePass;
}
protected ValueSet resolveBindingReference(DomainResource ctxt, String reference, String uri) {
if (reference != null) {
if (reference.startsWith("#")) {
for (Resource c : ctxt.getContained()) {
if (c.getId().equals(reference.substring(1)) && (c instanceof ValueSet))
return (ValueSet) c;
}
return null;
} else {
long t = System.nanoTime();
ValueSet fr = context.fetchResource(ValueSet.class, reference);
if (fr == null) {
if (!Utilities.isAbsoluteUrl(reference)) {
reference = resolve(uri, reference);
fr = context.fetchResource(ValueSet.class, reference);
}
}
if (fr == null)
fr = ValueSetUtilities.generateImplicitValueSet(reference);
timeTracker.tx(t, System.nanoTime());
return fr;
}
} else
return null;
}
private String resolve(String uri, String ref) {
if (isBlank(uri)) {
return ref;
}
String[] up = uri.split("\\/");
String[] rp = ref.split("\\/");
if (context.getResourceNames().contains(up[up.length - 2]) && context.getResourceNames().contains(rp[0])) {
StringBuilder b = new StringBuilder();
for (int i = 0; i < up.length - 2; i++) {
b.append(up[i]);
b.append("/");
}
b.append(ref);
return b.toString();
} else
return ref;
}
protected String describeReference(String reference) {
if (reference == null)
return "null";
return reference;
}
}

View File

@ -0,0 +1,58 @@
package org.hl7.fhir.validation;
public class TimeTracker {
private long overall = 0;
private long txTime = 0;
private long sdTime = 0;
private long loadTime = 0;
private long fpeTime = 0;
public long getOverall() {
return overall;
}
public long getTxTime() {
return txTime;
}
public long getSdTime() {
return sdTime;
}
public long getLoadTime() {
return loadTime;
}
public long getFpeTime() {
return fpeTime;
}
public void load(long t, long nanoTime) {
// TODO Auto-generated method stub
}
public void overall(long t, long nanoTime) {
// TODO Auto-generated method stub
}
public void tx(long t, long nanoTime) {
// TODO Auto-generated method stub
}
public void sd(long t, long nanoTime) {
// TODO Auto-generated method stub
}
public void fpe(long t, long nanoTime) {
// TODO Auto-generated method stub
fpeTime = fpeTime + (System.nanoTime() - t);
}
public void reset() {
overall = 0;
txTime = 0;
sdTime = 0;
loadTime = 0;
fpeTime = 0;
}
}

View File

@ -94,58 +94,6 @@ public class EnableWhenEvaluator {
}
}
public static class MeasurePair {
private MeasureReportGroupComponent g;
private Element a;
public MeasurePair(MeasureReportGroupComponent g, Element a) {
super();
this.g = g;
this.a = a;
}
public MeasureReportGroupComponent getGroup() {
return g;
}
public Element getA() {
return a;
}
}
public static class MStack extends ArrayList<MeasurePair> {
private static final long serialVersionUID = 1L;
private Measure m;
private Element a;
public MStack(Measure m, Element a) {
super();
this.m = m;
this.a = a;
}
public Measure getM() {
return m;
}
public Element getA() {
return a;
}
public MStack push(MeasureReportGroupComponent g, Element a) {
MStack self = new MStack(this.m, this.a);
self.addAll(this);
self.add(new MeasurePair(g, a));
return self;
}
}
public static class EnableWhenResult {
private final boolean enabled;
private final QuestionnaireItemEnableWhenComponent enableWhenCondition;

View File

@ -0,0 +1,359 @@
package org.hl7.fhir.validation.instance.type;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.convertors.VersionConvertor_30_50;
import org.hl7.fhir.convertors.VersionConvertor_40_50;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.JsonParser;
import org.hl7.fhir.r5.elementmodel.ObjectConverter;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.FhirPublication;
import org.hl7.fhir.r5.model.Library;
import org.hl7.fhir.r5.model.Measure;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.Measure.MeasureGroupComponent;
import org.hl7.fhir.r5.model.Measure.MeasureGroupPopulationComponent;
import org.hl7.fhir.r5.model.Measure.MeasureGroupStratifierComponent;
import org.hl7.fhir.r5.utils.NarrativeGenerator;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.validation.instance.utils.NodeStack;
import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.TimeTracker;
import org.hl7.fhir.validation.instance.utils.MeasureContext;
import org.hl7.fhir.validation.instance.utils.ValidatorHostContext;
public class MeasureValidator extends BaseValidator {
public MeasureValidator(IWorkerContext context, TimeTracker timeTracker) {
super(context);
source = Source.InstanceValidator;
this.timeTracker = timeTracker;
}
public void validateMeasure(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element element, NodeStack stack) throws FHIRException {
MeasureContext mctxt = new MeasureContext();
List<Element> libs = element.getChildrenByName("library");
for (Element lib : libs) {
String ref = lib.isPrimitive() ? lib.primitiveValue() : lib.getChildValue("reference");
if (!Utilities.noString(ref)) {
Library l = context.fetchResource(Library.class, ref);
if (hint(errors, IssueType.NOTFOUND, lib.line(), lib.col(), stack.getLiteralPath(), l != null, I18nConstants.MEASURE_M_LIB_UNKNOWN, ref)) {
mctxt.seeLibrary(l);
}
}
}
List<Element> groups = element.getChildrenByName("group");
if (warning(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), groups.size() > 0, I18nConstants.MEASURE_M_NO_GROUPS)) {
int c = 0;
for (Element group : groups) {
NodeStack ns = stack.push(group, c, null, null);
warning(errors, IssueType.REQUIRED, group.line(), group.col(), ns.getLiteralPath(), groups.size() ==1 || group.hasChild("code"), I18nConstants.MEASURE_M_GROUP_CODE);
warning(errors, IssueType.REQUIRED, group.line(), group.col(), ns.getLiteralPath(), group.hasChildren("population"), I18nConstants.MEASURE_M_GROUP_POP);
int c1 = 0;
List<Element> pl = group.getChildrenByName("population");
for (Element p : pl) {
NodeStack ns2 = ns.push(p, c1, null, null);
warning(errors, IssueType.REQUIRED, p.line(), p.col(), ns2.getLiteralPath(), pl.size() == 1 || p.hasChild("code"), I18nConstants.MEASURE_M_GROUP_POP_NO_CODE);
c1++;
}
warning(errors, IssueType.REQUIRED, group.line(), group.col(), ns.getLiteralPath(), group.hasChildren("stratifier"), I18nConstants.MEASURE_M_GROUP_STRATA);
c1 = 0;
List<Element> stl = group.getChildrenByName("stratifier");
for (Element st : stl) {
NodeStack ns2 = ns.push(st, c1, null, null);
warning(errors, IssueType.REQUIRED, st.line(), st.col(), ns2.getLiteralPath(), stl.size() == 1 || st.hasChild("code"), I18nConstants.MEASURE_M_GROUP_STRATA_NO_CODE);
if (st.hasChild("criteria")) {
Element crit = st.getNamedChild("criteria");
NodeStack nsc = ns2.push(crit, -1, null, null);
validateMeasureCriteria(hostContext, errors, mctxt, crit, nsc);
}
int c2 = 0;
List<Element> cpl = group.getChildrenByName("component");
for (Element cp : cpl) {
NodeStack ns3 = ns2.push(cp, c2, null, null);
warning(errors, IssueType.REQUIRED, cp.line(), cp.col(), ns3.getLiteralPath(), cpl.size() == 1 || cp.hasChild("code"), I18nConstants.MEASURE_M_GROUP_STRATA_COMP_NO_CODE);
if (cp.hasChild("criteria")) {
Element crit = cp.getNamedChild("criteria");
NodeStack nsc = ns3.push(crit, -1, null, null);
validateMeasureCriteria(hostContext, errors, mctxt, crit, nsc);
}
c2++;
}
c1++;
}
c++;
}
}
}
private void validateMeasureCriteria(ValidatorHostContext hostContext, List<ValidationMessage> errors, MeasureContext mctxt, Element crit, NodeStack nsc) {
// TODO Auto-generated method stub
}
// ---------------------------------------------------------------------------------------------------------------------------------------------------------
public void validateMeasureReport(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element element, NodeStack stack) throws FHIRException {
Element m = element.getNamedChild("measure");
String measure = null;
if (m != null) {
/*
* q.getValue() is correct for R4 content, but we'll also accept the second
* option just in case we're validating raw STU3 content. Being lenient here
* isn't the end of the world since if someone is actually doing the reference
* wrong in R4 content it'll get flagged elsewhere by the validator too
*/
if (isNotBlank(m.getValue())) {
measure = m.getValue();
} else if (isNotBlank(m.getChildValue("reference"))) {
measure = m.getChildValue("reference");
}
}
if (hint(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), measure != null, I18nConstants.MEASURE_MR_M_NONE)) {
long t = System.nanoTime();
Measure msrc = measure.startsWith("#") ? loadMeasure(element, measure.substring(1)) : context.fetchResource(Measure.class, measure);
if (warning(errors, IssueType.REQUIRED, m.line(), m.col(), stack.getLiteralPath(), msrc != null, I18nConstants.MEASURE_MR_M_NOTFOUND, measure)) {
boolean inComplete = !"complete".equals(element.getNamedChildValue("status"));
validateMeasureReportGroups(hostContext, msrc, errors, element, stack, inComplete);
}
}
}
private Measure loadMeasure(Element resource, String id) throws FHIRException {
try {
for (Element contained : resource.getChildren("contained")) {
if (contained.getIdBase().equals(id)) {
FhirPublication v = FhirPublication.fromCode(context.getVersion());
ByteArrayOutputStream bs = new ByteArrayOutputStream();
new JsonParser(context).compose(contained, bs, OutputStyle.NORMAL, id);
byte[] json = bs.toByteArray();
switch (v) {
case DSTU1:
throw new FHIRException(context.formatMessage(I18nConstants.UNSUPPORTED_VERSION_R1));
case DSTU2:
throw new FHIRException(context.formatMessage(I18nConstants.UNSUPPORTED_VERSION_R2));
case DSTU2016May:
throw new FHIRException(context.formatMessage(I18nConstants.UNSUPPORTED_VERSION_R2B));
case STU3:
org.hl7.fhir.dstu3.model.Resource r3 = new org.hl7.fhir.dstu3.formats.JsonParser().parse(json);
Resource r5 = VersionConvertor_30_50.convertResource(r3, false);
if (r5 instanceof Measure)
return (Measure) r5;
else
return null;
case R4:
org.hl7.fhir.r4.model.Resource r4 = new org.hl7.fhir.r4.formats.JsonParser().parse(json);
r5 = VersionConvertor_40_50.convertResource(r4);
if (r5 instanceof Measure)
return (Measure) r5;
else
return null;
case R5:
r5 = new org.hl7.fhir.r5.formats.JsonParser().parse(json);
if (r5 instanceof Measure)
return (Measure) r5;
else
return null;
}
}
}
return null;
} catch (IOException e) {
throw new FHIRException(e);
}
}
private void validateMeasureReportGroups(ValidatorHostContext hostContext, Measure m, List<ValidationMessage> errors, Element mr, NodeStack stack, boolean inProgress) {
NarrativeGenerator gen = new NarrativeGenerator(null, null, context);
List<MeasureGroupComponent> groups = new ArrayList<MeasureGroupComponent>();
List<Element> glist = mr.getChildrenByName("group");
if (glist.size() == 1 && m.getGroup().size() == 1) {
// if there's only one group, it can be ((and usually is) anonymous)
// but we still check that the code, if both have one, is consistent.
Element mrg = glist.get(0);
NodeStack ns = stack.push(mrg, 0, mrg.getProperty().getDefinition(), mrg.getProperty().getDefinition());
if (m.getGroupFirstRep().hasCode() && mrg.hasChild("code")) {
CodeableConcept cc = ObjectConverter.readAsCodeableConcept(mrg.getNamedChild("code"));
if (rule(errors, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), ns.getLiteralPath(), hasUseableCode(cc), I18nConstants.MEASURE_MR_GRP_NO_USABLE_CODE)) {
rule(errors, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), ns.getLiteralPath(), cc.matches(m.getGroupFirstRep().getCode()), I18nConstants.MEASURE_MR_GRP_NO_WRONG_CODE, gen.gen(cc), gen.gen(m.getGroupFirstRep().getCode()));
}
}
validateMeasureReportGroup(hostContext, m.getGroupFirstRep(), errors, mr, mrg, ns, inProgress, gen);
} else {
int i = 0;
for (Element mrg : glist) {
NodeStack ns = stack.push(mrg, i, mrg.getProperty().getDefinition(), mrg.getProperty().getDefinition());
CodeableConcept cc = ObjectConverter.readAsCodeableConcept(mrg.getNamedChild("code"));
if (rule(errors, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), ns.getLiteralPath(), cc != null, I18nConstants.MEASURE_MR_GRP_NO_CODE)) {
MeasureGroupComponent mg = getGroupForCode(cc, m);
if (rule(errors, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), ns.getLiteralPath(), mg != null, I18nConstants.MEASURE_MR_GRP_UNK_CODE)) {
if (rule(errors, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), ns.getLiteralPath(), !groups.contains(mg), I18nConstants.MEASURE_MR_GRP_DUPL_CODE)) {
groups.add(mg);
validateMeasureReportGroup(hostContext, mg, errors, mr, mrg, ns, inProgress, gen);
}
}
}
i++;
}
for (MeasureGroupComponent mg : m.getGroup()) {
if (!groups.contains(mg)) {
rule(errors, IssueType.BUSINESSRULE, mr.line(), mr.col(), stack.getLiteralPath(), groups.contains(mg), I18nConstants.MEASURE_MR_GRP_MISSING_BY_CODE, gen.gen(mg.getCode()));
}
}
}
}
private void validateMeasureReportGroup(ValidatorHostContext hostContext, MeasureGroupComponent mg, List<ValidationMessage> errors, Element mr, Element mrg, NodeStack ns, boolean inProgress, NarrativeGenerator gen) {
validateMeasureReportGroupPopulations(hostContext, mg, errors, mr, mrg, ns, inProgress, gen);
// validate the score
validateMeasureReportGroupStratifiers(hostContext, mg, errors, mr, mrg, ns, inProgress, gen);
// validate the stratifiers
}
private void validateMeasureReportGroupPopulations(ValidatorHostContext hostContext, MeasureGroupComponent mg, List<ValidationMessage> errors, Element mr, Element mrg, NodeStack stack, boolean inProgress, NarrativeGenerator gen) {
// there must be a population for each population defined in the measure, and no 4others.
List<MeasureGroupPopulationComponent> pops = new ArrayList<MeasureGroupPopulationComponent>();
List<Element> plist = mrg.getChildrenByName("population");
int i = 0;
for (Element mrgp : plist) {
NodeStack ns = stack.push(mrgp, i, mrgp.getProperty().getDefinition(), mrgp.getProperty().getDefinition());
CodeableConcept cc = ObjectConverter.readAsCodeableConcept(mrgp.getNamedChild("code"));
if (rule(errors, IssueType.BUSINESSRULE, mrgp.line(), mrgp.col(), ns.getLiteralPath(), cc != null, I18nConstants.MEASURE_MR_GRP_POP_NO_CODE)) {
MeasureGroupPopulationComponent mgp = getGroupPopForCode(cc, mg);
if (rule(errors, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), ns.getLiteralPath(), mgp != null, I18nConstants.MEASURE_MR_GRP_POP_UNK_CODE)) {
if (rule(errors, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), ns.getLiteralPath(), !pops.contains(mgp), I18nConstants.MEASURE_MR_GRP_POP_DUPL_CODE)) {
pops.add(mgp);
validateMeasureReportGroupPopulation(hostContext, mgp, errors, mr, mrgp, ns, inProgress);
}
}
}
i++;
}
for (MeasureGroupPopulationComponent mgp : mg.getPopulation()) {
if (!pops.contains(mgp)) {
rule(errors, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), stack.getLiteralPath(), pops.contains(mg), I18nConstants.MEASURE_MR_GRP_MISSING_BY_CODE, gen.gen(mgp.getCode()));
}
}
}
private void validateMeasureReportGroupPopulation(ValidatorHostContext hostContext, MeasureGroupPopulationComponent mgp, List<ValidationMessage> errors, Element mr, Element mrgp, NodeStack ns, boolean inProgress) {
List<Element> sr = mrgp.getChildrenByName("subjectResults");
if ("subject-list".equals(mr.getChildValue("type"))) {
try {
int c = Integer.parseInt(mrgp.getChildValue("count"));
rule(errors, IssueType.BUSINESSRULE, mrgp.line(), mrgp.col(), ns.getLiteralPath(), c == sr.size(), I18nConstants.MEASURE_MR_GRP_POP_COUNT_MISMATCH, c, sr.size());
} catch (Exception e) {
// nothing; that'll be because count is not valid, and that's a different error or its missing and we don't care
}
} else {
rule(errors, IssueType.BUSINESSRULE, mrgp.line(), mrgp.col(), ns.getLiteralPath(), sr.size() == 0, I18nConstants.MEASURE_MR_GRP_POP_NO_SUBJECTS);
warning(errors, IssueType.BUSINESSRULE, mrgp.line(), mrgp.col(), ns.getLiteralPath(), mrgp.hasChild("count"), I18nConstants.MEASURE_MR_GRP_POP_NO_COUNT);
}
}
private void validateMeasureReportGroupStratifiers(ValidatorHostContext hostContext, MeasureGroupComponent mg, List<ValidationMessage> errors, Element mr, Element mrg, NodeStack stack, boolean inProgress, NarrativeGenerator gen) {
// there must be a population for each population defined in the measure, and no 4others.
List<MeasureGroupStratifierComponent> strats = new ArrayList<>();
List<Element> slist = mrg.getChildrenByName("stratifier");
int i = 0;
for (Element mrgs : slist) {
NodeStack ns = stack.push(mrgs, i, mrgs.getProperty().getDefinition(), mrgs.getProperty().getDefinition());
CodeableConcept cc = ObjectConverter.readAsCodeableConcept(mrgs.getNamedChild("code"));
if (rule(errors, IssueType.BUSINESSRULE, mrgs.line(), mrgs.col(), ns.getLiteralPath(), cc != null, I18nConstants.MEASURE_MR_GRP_POP_NO_CODE)) {
MeasureGroupStratifierComponent mgs = getGroupStratifierForCode(cc, mg);
if (rule(errors, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), ns.getLiteralPath(), mgs != null, I18nConstants.MEASURE_MR_GRP_POP_UNK_CODE)) {
if (rule(errors, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), ns.getLiteralPath(), !strats.contains(mgs), I18nConstants.MEASURE_MR_GRP_POP_DUPL_CODE)) {
strats.add(mgs);
validateMeasureReportGroupStratifier(hostContext, mgs, errors, mr, mrgs, ns, inProgress);
}
}
}
i++;
}
for (MeasureGroupStratifierComponent mgs : mg.getStratifier()) {
if (!strats.contains(mgs)) {
rule(errors, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), stack.getLiteralPath(), strats.contains(mg), I18nConstants.MEASURE_MR_GRP_MISSING_BY_CODE, gen.gen(mgs.getCode()));
}
}
}
private void validateMeasureReportGroupStratifier(ValidatorHostContext hostContext, MeasureGroupStratifierComponent mgs, List<ValidationMessage> errors, Element mr, Element mrgs, NodeStack ns, boolean inProgress) {
// TODO Auto-generated method stub
}
private MeasureGroupStratifierComponent getGroupStratifierForCode(CodeableConcept cc, MeasureGroupComponent mg) {
for (MeasureGroupStratifierComponent t : mg.getStratifier()) {
if (t.hasCode()) {
for (Coding c : t.getCode().getCoding()) {
if (cc.hasCoding(c.getSystem(), c.getCode())) {
return t;
}
}
if (!cc.hasCoding() && !t.getCode().hasCoding()) {
if (cc.hasText() && t.getCode().hasText()) {
if (cc.getText().equals(t.getCode().getText())) {
return t;
}
}
}
}
}
return null;
}
private boolean hasUseableCode(CodeableConcept cc) {
for (Coding c : cc.getCoding()) {
if (c.hasSystem() && c.hasCode()) {
return true;
}
}
return false;
}
private MeasureGroupPopulationComponent getGroupPopForCode(CodeableConcept cc, MeasureGroupComponent mg) {
for (MeasureGroupPopulationComponent t : mg.getPopulation()) {
if (t.hasCode()) {
for (Coding c : t.getCode().getCoding()) {
if (cc.hasCoding(c.getSystem(), c.getCode())) {
return t;
}
}
}
}
return null;
}
private MeasureGroupComponent getGroupForCode(CodeableConcept cc, Measure m) {
for (MeasureGroupComponent t : m.getGroup()) {
if (t.hasCode()) {
for (Coding c : t.getCode().getCoding()) {
if (cc.hasCoding(c.getSystem(), c.getCode())) {
return t;
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,710 @@
package org.hl7.fhir.validation.instance.type;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.convertors.VersionConvertor_10_50;
import org.hl7.fhir.convertors.VersionConvertor_14_50;
import org.hl7.fhir.convertors.VersionConvertor_30_50;
import org.hl7.fhir.convertors.VersionConvertor_40_50;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.JsonParser;
import org.hl7.fhir.r5.elementmodel.ObjectConverter;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.DateType;
import org.hl7.fhir.r5.model.FhirPublication;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.Questionnaire;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.TimeType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.Enumerations.FHIRVersion;
import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemAnswerOptionComponent;
import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemComponent;
import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemType;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.TimeTracker;
import org.hl7.fhir.validation.instance.EnableWhenEvaluator;
import org.hl7.fhir.validation.instance.EnableWhenEvaluator.QStack;
import org.hl7.fhir.validation.instance.utils.NodeStack;
import org.hl7.fhir.validation.instance.utils.ValidatorHostContext;
import ca.uhn.fhir.util.ObjectUtil;
public class QuestionnaireValidator extends BaseValidator {
private EnableWhenEvaluator myEnableWhenEvaluator;
private FHIRPathEngine fpe;
public QuestionnaireValidator(IWorkerContext context, EnableWhenEvaluator myEnableWhenEvaluator, FHIRPathEngine fpe, TimeTracker timeTracker) {
super(context);
source = Source.InstanceValidator;
this.myEnableWhenEvaluator = myEnableWhenEvaluator;
this.fpe = fpe;
this.timeTracker = timeTracker;
}
public void validateQuestionannaire(List<ValidationMessage> errors, Element element, Element element2, NodeStack stack) {
ArrayList<Element> parents = new ArrayList<>();
parents.add(element);
validateQuestionannaireItem(errors, element, element, stack, parents);
}
private void validateQuestionannaireItem(List<ValidationMessage> errors, Element element, Element questionnaire, NodeStack stack, List<Element> parents) {
List<Element> list = getItems(element);
for (int i = 0; i < list.size(); i++) {
Element e = list.get(i);
NodeStack ns = stack.push(e, i, e.getProperty().getDefinition(), e.getProperty().getDefinition());
validateQuestionnaireElement(errors, ns, questionnaire, e, parents);
List<Element> np = new ArrayList<Element>();
np.add(e);
np.addAll(parents);
validateQuestionannaireItem(errors, e, questionnaire, ns, np);
}
}
private void validateQuestionnaireElement(List<ValidationMessage> errors, NodeStack ns, Element questionnaire, Element item, List<Element> parents) {
// R4+
if ((FHIRVersion.isR4Plus(context.getVersion())) && (item.hasChildren("enableWhen"))) {
List<Element> ewl = item.getChildren("enableWhen");
for (Element ew : ewl) {
String ql = ew.getNamedChildValue("question");
if (rule(errors, IssueType.BUSINESSRULE, ns.getLiteralPath(), ql != null, I18nConstants.QUESTIONNAIRE_Q_ENABLEWHEN_NOLINK)) {
Element tgt = getQuestionById(item, ql);
if (rule(errors, IssueType.BUSINESSRULE, ns.getLiteralPath(), tgt == null, I18nConstants.QUESTIONNAIRE_Q_ENABLEWHEN_ISINNER)) {
tgt = getQuestionById(questionnaire, ql);
if (rule(errors, IssueType.BUSINESSRULE, ns.getLiteralPath(), tgt != null, I18nConstants.QUESTIONNAIRE_Q_ENABLEWHEN_NOTARGET, ql)) {
if (rule(errors, IssueType.BUSINESSRULE, ns.getLiteralPath(), tgt != item, I18nConstants.QUESTIONNAIRE_Q_ENABLEWHEN_SELF)) {
if (!isBefore(item, tgt, parents)) {
warning(errors, IssueType.BUSINESSRULE, ns.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_Q_ENABLEWHEN_AFTER, ql);
}
}
}
}
}
}
}
}
private boolean isBefore(Element item, Element tgt, List<Element> parents) {
// we work up the list, looking for tgt in the children of the parents
if (parents.contains(tgt)) {
// actually, if the target is a parent, that's automatically ok
return true;
}
for (Element p : parents) {
int i = findIndex(p, item);
int t = findIndex(p, tgt);
if (i > -1 && t > -1) {
return i > t;
}
}
return false; // unsure... shouldn't ever get to this point;
}
private int findIndex(Element parent, Element descendant) {
for (int i = 0; i < parent.getChildren().size(); i++) {
if (parent.getChildren().get(i) == descendant || isChild(parent.getChildren().get(i), descendant))
return i;
}
return -1;
}
private boolean isChild(Element element, Element descendant) {
for (Element e : element.getChildren()) {
if (e == descendant)
return true;
if (isChild(e, descendant))
return true;
}
return false;
}
private Element getQuestionById(Element focus, String ql) {
List<Element> list = getItems(focus);
for (Element item : list) {
String v = item.getNamedChildValue("linkId");
if (ql.equals(v))
return item;
Element tgt = getQuestionById(item, ql);
if (tgt != null)
return tgt;
}
return null;
}
private List<Element> getItems(Element element) {
List<Element> list = new ArrayList<>();
element.getNamedChildren("item", list);
return list;
}
public void validateQuestionannaireResponse(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element element, NodeStack stack) throws FHIRException {
Element q = element.getNamedChild("questionnaire");
String questionnaire = null;
if (q != null) {
/*
* q.getValue() is correct for R4 content, but we'll also accept the second
* option just in case we're validating raw STU3 content. Being lenient here
* isn't the end of the world since if someone is actually doing the reference
* wrong in R4 content it'll get flagged elsewhere by the validator too
*/
if (isNotBlank(q.getValue())) {
questionnaire = q.getValue();
} else if (isNotBlank(q.getChildValue("reference"))) {
questionnaire = q.getChildValue("reference");
}
}
if (hint(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), questionnaire != null, I18nConstants.QUESTIONNAIRE_QR_Q_NONE)) {
long t = System.nanoTime();
Questionnaire qsrc = questionnaire.startsWith("#") ? loadQuestionnaire(element, questionnaire.substring(1)) : context.fetchResource(Questionnaire.class, questionnaire);
if (warning(errors, IssueType.REQUIRED, q.line(), q.col(), stack.getLiteralPath(), qsrc != null, I18nConstants.QUESTIONNAIRE_QR_Q_NOTFOUND, questionnaire)) {
boolean inProgress = "in-progress".equals(element.getNamedChildValue("status"));
validateQuestionannaireResponseItems(hostContext, qsrc, qsrc.getItem(), errors, element, stack, inProgress, element, new QStack(qsrc, element));
}
}
}
private Questionnaire loadQuestionnaire(Element resource, String id) throws FHIRException {
try {
for (Element contained : resource.getChildren("contained")) {
if (contained.getIdBase().equals(id)) {
FhirPublication v = FhirPublication.fromCode(context.getVersion());
ByteArrayOutputStream bs = new ByteArrayOutputStream();
new JsonParser(context).compose(contained, bs, OutputStyle.NORMAL, id);
byte[] json = bs.toByteArray();
switch (v) {
case DSTU1:
throw new FHIRException(context.formatMessage(I18nConstants.UNSUPPORTED_VERSION_R1));
case DSTU2:
org.hl7.fhir.dstu2.model.Resource r2 = new org.hl7.fhir.dstu2.formats.JsonParser().parse(json);
Resource r5 = VersionConvertor_10_50.convertResource(r2);
if (r5 instanceof Questionnaire)
return (Questionnaire) r5;
else
return null;
case DSTU2016May:
org.hl7.fhir.dstu2016may.model.Resource r2a = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(json);
r5 = VersionConvertor_14_50.convertResource(r2a);
if (r5 instanceof Questionnaire)
return (Questionnaire) r5;
else
return null;
case STU3:
org.hl7.fhir.dstu3.model.Resource r3 = new org.hl7.fhir.dstu3.formats.JsonParser().parse(json);
r5 = VersionConvertor_30_50.convertResource(r3, false);
if (r5 instanceof Questionnaire)
return (Questionnaire) r5;
else
return null;
case R4:
org.hl7.fhir.r4.model.Resource r4 = new org.hl7.fhir.r4.formats.JsonParser().parse(json);
r5 = VersionConvertor_40_50.convertResource(r4);
if (r5 instanceof Questionnaire)
return (Questionnaire) r5;
else
return null;
case R5:
r5 = new org.hl7.fhir.r5.formats.JsonParser().parse(json);
if (r5 instanceof Questionnaire)
return (Questionnaire) r5;
else
return null;
}
}
}
return null;
} catch (IOException e) {
throw new FHIRException(e);
}
}
private void validateQuestionnaireResponseItem(ValidatorHostContext hostContext, Questionnaire qsrc, QuestionnaireItemComponent qItem, List<ValidationMessage> errors, Element element, NodeStack stack, boolean inProgress, Element questionnaireResponseRoot, QStack qstack) {
String text = element.getNamedChildValue("text");
rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), Utilities.noString(text) || text.equals(qItem.getText()), I18nConstants.QUESTIONNAIRE_QR_ITEM_TEXT, qItem.getLinkId());
List<Element> answers = new ArrayList<Element>();
element.getNamedChildren("answer", answers);
if (inProgress)
warning(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), isAnswerRequirementFulfilled(qItem, answers), I18nConstants.QUESTIONNAIRE_QR_ITEM_MISSING, qItem.getLinkId());
else if (myEnableWhenEvaluator.isQuestionEnabled(hostContext, qItem, qstack, fpe)) {
rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), isAnswerRequirementFulfilled(qItem, answers), I18nConstants.QUESTIONNAIRE_QR_ITEM_MISSING, qItem.getLinkId());
} else if (!answers.isEmpty()) { // items without answers should be allowed, but not items with answers to questions that are disabled
// it appears that this is always a duplicate error - it will always already have been reported, so no need to report it again?
// GDG 2019-07-13
// rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), !isAnswerRequirementFulfilled(qItem, answers), I18nConstants.QUESTIONNAIRE_QR_ITEM_NOTENABLED, qItem.getLinkId());
}
if (answers.size() > 1)
rule(errors, IssueType.INVALID, answers.get(1).line(), answers.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), I18nConstants.QUESTIONNAIRE_QR_ITEM_ONLYONEA);
for (Element answer : answers) {
NodeStack ns = stack.push(answer, -1, null, null);
if (qItem.getType() != null) {
switch (qItem.getType()) {
case GROUP:
rule(errors, IssueType.STRUCTURE, answer.line(), answer.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_GROUP);
break;
case DISPLAY: // nothing
break;
case BOOLEAN:
validateQuestionnaireResponseItemType(errors, answer, ns, "boolean");
break;
case DECIMAL:
validateQuestionnaireResponseItemType(errors, answer, ns, "decimal");
break;
case INTEGER:
validateQuestionnaireResponseItemType(errors, answer, ns, "integer");
break;
case DATE:
validateQuestionnaireResponseItemType(errors, answer, ns, "date");
break;
case DATETIME:
validateQuestionnaireResponseItemType(errors, answer, ns, "dateTime");
break;
case TIME:
validateQuestionnaireResponseItemType(errors, answer, ns, "time");
break;
case STRING:
validateQuestionnaireResponseItemType(errors, answer, ns, "string");
break;
case TEXT:
validateQuestionnaireResponseItemType(errors, answer, ns, "text");
break;
case URL:
validateQuestionnaireResponseItemType(errors, answer, ns, "uri");
break;
case ATTACHMENT:
validateQuestionnaireResponseItemType(errors, answer, ns, "Attachment");
break;
case REFERENCE:
validateQuestionnaireResponseItemType(errors, answer, ns, "Reference");
break;
case QUANTITY:
if ("Quantity".equals(validateQuestionnaireResponseItemType(errors, answer, ns, "Quantity")))
if (qItem.hasExtension("???"))
validateQuestionnaireResponseItemQuantity(errors, answer, ns);
break;
case CHOICE:
String itemType = validateQuestionnaireResponseItemType(errors, answer, ns, "Coding", "date", "time", "integer", "string");
if (itemType != null) {
if (itemType.equals("Coding")) validateAnswerCode(errors, answer, ns, qsrc, qItem, false);
else if (itemType.equals("date")) checkOption(errors, answer, ns, qsrc, qItem, "date");
else if (itemType.equals("time")) checkOption(errors, answer, ns, qsrc, qItem, "time");
else if (itemType.equals("integer"))
checkOption(errors, answer, ns, qsrc, qItem, "integer");
else if (itemType.equals("string")) checkOption(errors, answer, ns, qsrc, qItem, "string");
}
break;
case OPENCHOICE:
itemType = validateQuestionnaireResponseItemType(errors, answer, ns, "Coding", "date", "time", "integer", "string");
if (itemType != null) {
if (itemType.equals("Coding")) validateAnswerCode(errors, answer, ns, qsrc, qItem, true);
else if (itemType.equals("date")) checkOption(errors, answer, ns, qsrc, qItem, "date");
else if (itemType.equals("time")) checkOption(errors, answer, ns, qsrc, qItem, "time");
else if (itemType.equals("integer"))
checkOption(errors, answer, ns, qsrc, qItem, "integer");
else if (itemType.equals("string"))
checkOption(errors, answer, ns, qsrc, qItem, "string", true);
}
break;
// case QUESTION:
case NULL:
// no validation
break;
}
}
if (qItem.getType() != QuestionnaireItemType.GROUP) {
// if it's a group, we already have an error before getting here, so no need to hammer away on that
validateQuestionannaireResponseItems(hostContext, qsrc, qItem.getItem(), errors, answer, stack, inProgress, questionnaireResponseRoot, qstack);
}
}
if (qItem.getType() == null) {
fail(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOTYPE, qItem.getLinkId());
} else if (qItem.getType() == QuestionnaireItemType.DISPLAY) {
List<Element> items = new ArrayList<Element>();
element.getNamedChildren("item", items);
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), items.isEmpty(), I18nConstants.QUESTIONNAIRE_QR_ITEM_DISPLAY, qItem.getLinkId());
} else if (qItem.getType() != QuestionnaireItemType.GROUP) {
List<Element> items = new ArrayList<Element>();
element.getNamedChildren("item", items);
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), items.isEmpty(), I18nConstants.QUESTIONNAIRE_QR_ITEM_GROUP_ANSWER, qItem.getLinkId());
} else {
validateQuestionannaireResponseItems(hostContext, qsrc, qItem.getItem(), errors, element, stack, inProgress, questionnaireResponseRoot, qstack);
}
}
private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, List<Element> answers) {
return !answers.isEmpty() || !qItem.getRequired() || qItem.getType() == QuestionnaireItemType.GROUP;
}
private void validateQuestionnaireResponseItem(ValidatorHostContext hostcontext, Questionnaire qsrc, QuestionnaireItemComponent qItem, List<ValidationMessage> errors, List<Element> elements, NodeStack stack, boolean inProgress, Element questionnaireResponseRoot, QStack qstack) {
if (elements.size() > 1)
rule(errors, IssueType.INVALID, elements.get(1).line(), elements.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), I18nConstants.QUESTIONNAIRE_QR_ITEM_ONLYONEI, qItem.getLinkId());
int i = 0;
for (Element element : elements) {
NodeStack ns = stack.push(element, i, null, null);
validateQuestionnaireResponseItem(hostcontext, qsrc, qItem, errors, element, ns, inProgress, questionnaireResponseRoot, qstack.push(qItem, element));
i++;
}
}
private int getLinkIdIndex(List<QuestionnaireItemComponent> qItems, String linkId) {
for (int i = 0; i < qItems.size(); i++) {
if (linkId.equals(qItems.get(i).getLinkId()))
return i;
}
return -1;
}
private void validateQuestionannaireResponseItems(ValidatorHostContext hostContext, Questionnaire qsrc, List<QuestionnaireItemComponent> qItems, List<ValidationMessage> errors, Element element, NodeStack stack, boolean inProgress, Element questionnaireResponseRoot, QStack qstack) {
List<Element> items = new ArrayList<Element>();
element.getNamedChildren("item", items);
// now, sort into stacks
Map<String, List<Element>> map = new HashMap<String, List<Element>>();
int lastIndex = -1;
for (Element item : items) {
String linkId = item.getNamedChildValue("linkId");
if (rule(errors, IssueType.REQUIRED, item.line(), item.col(), stack.getLiteralPath(), !Utilities.noString(linkId), I18nConstants.QUESTIONNAIRE_QR_ITEM_NOLINKID)) {
int index = getLinkIdIndex(qItems, linkId);
if (index == -1) {
QuestionnaireItemComponent qItem = findQuestionnaireItem(qsrc, linkId);
if (qItem != null) {
rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index > -1, misplacedItemError(qItem));
NodeStack ns = stack.push(item, -1, null, null);
validateQuestionnaireResponseItem(hostContext, qsrc, qItem, errors, item, ns, inProgress, questionnaireResponseRoot, qstack.push(qItem, item));
} else
rule(errors, IssueType.NOTFOUND, item.line(), item.col(), stack.getLiteralPath(), index > -1, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOTFOUND, linkId);
} else {
rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index >= lastIndex, I18nConstants.QUESTIONNAIRE_QR_ITEM_ORDER);
lastIndex = index;
// If an item has a child called "linkId" but no child called "answer",
// we'll treat it as not existing for the purposes of enableWhen validation
if (item.hasChildren("answer") || item.hasChildren("item")) {
List<Element> mapItem = map.computeIfAbsent(linkId, key -> new ArrayList<>());
mapItem.add(item);
}
}
}
}
// ok, now we have a list of known items, grouped by linkId. We've made an error for anything out of order
for (QuestionnaireItemComponent qItem : qItems) {
List<Element> mapItem = map.get(qItem.getLinkId());
validateQuestionnaireResponseItem(hostContext, qsrc, errors, element, stack, inProgress, questionnaireResponseRoot, qItem, mapItem, qstack);
}
}
public void validateQuestionnaireResponseItem(ValidatorHostContext hostContext, Questionnaire qsrc, List<ValidationMessage> errors, Element element, NodeStack stack, boolean inProgress, Element questionnaireResponseRoot, QuestionnaireItemComponent qItem, List<Element> mapItem, QStack qstack) {
boolean enabled = myEnableWhenEvaluator.isQuestionEnabled(hostContext, qItem, qstack, fpe);
if (mapItem != null) {
if (!enabled) {
int i = 0;
for (Element e : mapItem) {
NodeStack ns = stack.push(e, i, e.getProperty().getDefinition(), e.getProperty().getDefinition());
rule(errors, IssueType.INVALID, e.line(), e.col(), ns.getLiteralPath(), enabled, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOTENABLED2, qItem.getLinkId());
i++;
}
}
// Recursively validate child items
validateQuestionnaireResponseItem(hostContext, qsrc, qItem, errors, mapItem, stack, inProgress, questionnaireResponseRoot, qstack);
} else {
// item is missing, is the question enabled?
if (enabled && qItem.getRequired()) {
String message = context.formatMessage(I18nConstants.QUESTIONNAIRE_QR_ITEM_MISSING, qItem.getLinkId());
if (inProgress) {
warning(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), false, message);
} else {
rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), false, message);
}
}
}
}
private String misplacedItemError(QuestionnaireItemComponent qItem) {
return qItem.hasLinkId() ? String.format("Structural Error: item with linkid %s is in the wrong place", qItem.getLinkId()) : "Structural Error: item is in the wrong place";
}
private void validateQuestionnaireResponseItemQuantity(List<ValidationMessage> errors, Element answer, NodeStack stack) {
}
private String validateQuestionnaireResponseItemType(List<ValidationMessage> errors, Element element, NodeStack stack, String... types) {
List<Element> values = new ArrayList<Element>();
element.getNamedChildrenWithWildcard("value[x]", values);
for (int i = 0; i < types.length; i++) {
if (types[i].equals("text")) {
types[i] = "string";
}
}
if (values.size() > 0) {
NodeStack ns = stack.push(values.get(0), -1, null, null);
CommaSeparatedStringBuilder l = new CommaSeparatedStringBuilder();
for (String s : types) {
l.append(s);
if (values.get(0).getName().equals("value" + Utilities.capitalize(s)))
return (s);
}
if (types.length == 1)
rule(errors, IssueType.STRUCTURE, values.get(0).line(), values.get(0).col(), ns.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_WRONGTYPE, types[0]);
else
rule(errors, IssueType.STRUCTURE, values.get(0).line(), values.get(0).col(), ns.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_WRONGTYPE2, l.toString());
}
return null;
}
private QuestionnaireItemComponent findQuestionnaireItem(Questionnaire qSrc, String linkId) {
return findItem(qSrc.getItem(), linkId);
}
private QuestionnaireItemComponent findItem(List<QuestionnaireItemComponent> list, String linkId) {
for (QuestionnaireItemComponent item : list) {
if (linkId.equals(item.getLinkId()))
return item;
QuestionnaireItemComponent result = findItem(item.getItem(), linkId);
if (result != null)
return result;
}
return null;
}
private void validateAnswerCode(List<ValidationMessage> errors, Element value, NodeStack stack, Questionnaire qSrc, String ref, boolean theOpenChoice) {
ValueSet vs = resolveBindingReference(qSrc, ref, qSrc.getUrl());
if (warning(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), vs != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(ref))) {
try {
Coding c = ObjectConverter.readAsCoding(value);
if (isBlank(c.getCode()) && isBlank(c.getSystem()) && isNotBlank(c.getDisplay())) {
if (theOpenChoice) {
return;
}
}
long t = System.nanoTime();
ValidationResult res = context.validateCode(new ValidationOptions(stack.getWorkingLang()), c, vs);
timeTracker.tx(t, System.nanoTime());
if (!res.isOk()) {
txRule(errors, res.getTxLink(), IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_BADOPTION, c.getSystem(), c.getCode());
} else if (res.getSeverity() != null) {
super.addValidationMessage(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), res.getMessage(), res.getSeverity(), Source.TerminologyEngine);
}
} catch (Exception e) {
warning(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_CODING, e.getMessage());
}
}
}
private void validateAnswerCode(List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean theOpenChoice) {
Element v = answer.getNamedChild("valueCoding");
NodeStack ns = stack.push(v, -1, null, null);
if (qItem.getAnswerOption().size() > 0)
checkCodingOption(errors, answer, stack, qSrc, qItem, theOpenChoice);
// validateAnswerCode(errors, v, stack, qItem.getOption());
else if (qItem.hasAnswerValueSet())
validateAnswerCode(errors, v, stack, qSrc, qItem.getAnswerValueSet(), theOpenChoice);
else
hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOOPTIONS);
}
private void checkOption(List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, String type) {
checkOption(errors, answer, stack, qSrc, qItem, type, false);
}
private void checkOption(List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, String type, boolean openChoice) {
if (type.equals("integer")) checkIntegerOption(errors, answer, stack, qSrc, qItem, openChoice);
else if (type.equals("date")) checkDateOption(errors, answer, stack, qSrc, qItem, openChoice);
else if (type.equals("time")) checkTimeOption(errors, answer, stack, qSrc, qItem, openChoice);
else if (type.equals("string")) checkStringOption(errors, answer, stack, qSrc, qItem, openChoice);
else if (type.equals("Coding")) checkCodingOption(errors, answer, stack, qSrc, qItem, openChoice);
}
private void checkIntegerOption(List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
Element v = answer.getNamedChild("valueInteger");
NodeStack ns = stack.push(v, -1, null, null);
if (qItem.getAnswerOption().size() > 0) {
List<IntegerType> list = new ArrayList<IntegerType>();
for (QuestionnaireItemAnswerOptionComponent components : qItem.getAnswerOption()) {
try {
list.add(components.getValueIntegerType());
} catch (FHIRException e) {
// If it's the wrong type, just keep going
}
}
if (list.isEmpty() && !openChoice) {
rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOOPTIONSINTEGER);
} else {
boolean found = false;
for (IntegerType item : list) {
if (item.getValue() == Integer.parseInt(v.primitiveValue())) {
found = true;
break;
}
}
if (!found) {
rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOINTEGER, v.primitiveValue());
}
}
} else
hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_INTNOOPTIONS);
}
private void checkDateOption(List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
Element v = answer.getNamedChild("valueDate");
NodeStack ns = stack.push(v, -1, null, null);
if (qItem.getAnswerOption().size() > 0) {
List<DateType> list = new ArrayList<DateType>();
for (QuestionnaireItemAnswerOptionComponent components : qItem.getAnswerOption()) {
try {
list.add(components.getValueDateType());
} catch (FHIRException e) {
// If it's the wrong type, just keep going
}
}
if (list.isEmpty() && !openChoice) {
rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOOPTIONSDATE);
} else {
boolean found = false;
for (DateType item : list) {
if (item.getValue().equals(v.primitiveValue())) {
found = true;
break;
}
}
if (!found) {
rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, I18nConstants.QUESTIONNAIRE_QR_ITEM_NODATE, v.primitiveValue());
}
}
} else
hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_DATENOOPTIONS);
}
private void checkTimeOption(List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
Element v = answer.getNamedChild("valueTime");
NodeStack ns = stack.push(v, -1, null, null);
if (qItem.getAnswerOption().size() > 0) {
List<TimeType> list = new ArrayList<TimeType>();
for (QuestionnaireItemAnswerOptionComponent components : qItem.getAnswerOption()) {
try {
list.add(components.getValueTimeType());
} catch (FHIRException e) {
// If it's the wrong type, just keep going
}
}
if (list.isEmpty() && !openChoice) {
rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOOPTIONSTIME);
} else {
boolean found = false;
for (TimeType item : list) {
if (item.getValue().equals(v.primitiveValue())) {
found = true;
break;
}
}
if (!found) {
rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOTIME, v.primitiveValue());
}
}
} else
hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_TIMENOOPTIONS);
}
private void checkStringOption(List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
Element v = answer.getNamedChild("valueString");
NodeStack ns = stack.push(v, -1, null, null);
if (qItem.getAnswerOption().size() > 0) {
List<StringType> list = new ArrayList<StringType>();
for (QuestionnaireItemAnswerOptionComponent components : qItem.getAnswerOption()) {
try {
if (components.getValue() != null) {
list.add(components.getValueStringType());
}
} catch (FHIRException e) {
// If it's the wrong type, just keep going
}
}
if (!openChoice) {
if (list.isEmpty()) {
rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOOPTIONSSTRING);
} else {
boolean found = false;
for (StringType item : list) {
if (item.getValue().equals((v.primitiveValue()))) {
found = true;
break;
}
}
if (!found) {
rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOSTRING, v.primitiveValue());
}
}
}
} else {
hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_STRINGNOOPTIONS);
}
}
private void checkCodingOption(List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
Element v = answer.getNamedChild("valueCoding");
String system = v.getNamedChildValue("system");
String code = v.getNamedChildValue("code");
NodeStack ns = stack.push(v, -1, null, null);
if (qItem.getAnswerOption().size() > 0) {
List<Coding> list = new ArrayList<Coding>();
for (QuestionnaireItemAnswerOptionComponent components : qItem.getAnswerOption()) {
try {
if (components.getValue() != null) {
list.add(components.getValueCoding());
}
} catch (FHIRException e) {
// If it's the wrong type, just keep going
}
}
if (list.isEmpty() && !openChoice) {
rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOOPTIONSCODING);
} else {
boolean found = false;
for (Coding item : list) {
if (ObjectUtil.equals(item.getSystem(), system) && ObjectUtil.equals(item.getCode(), code)) {
found = true;
break;
}
}
if (!found) {
rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOCODING, system, code);
}
}
} else
hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_CODINGNOOPTIONS);
}
}

View File

@ -0,0 +1,12 @@
package org.hl7.fhir.validation.instance.utils;
import org.hl7.fhir.r5.model.Library;
public class MeasureContext {
public void seeLibrary(Library l) {
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,177 @@
package org.hl7.fhir.validation.instance.utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.utilities.Utilities;
public class NodeStack {
protected IWorkerContext context;
private ElementDefinition definition;
private Element element;
private ElementDefinition extension;
private String literalPath; // xpath format
private List<String> logicalPaths; // dotted format, various entry points
private NodeStack parent;
private ElementDefinition type;
private String workingLang;
private Map<String, Element> ids;
public NodeStack(IWorkerContext context) {
this.context = context;
}
public NodeStack(IWorkerContext context, Element element, String validationLanguage) {
this.context = context;
ids = new HashMap<>();
this.element = element;
literalPath = element.getName();
workingLang = validationLanguage;
if (!element.getName().equals(element.fhirType())) {
logicalPaths = new ArrayList<>();
logicalPaths.add(element.fhirType());
}
}
public NodeStack(IWorkerContext context, Element element, String refPath, String validationLanguage) {
this.context = context;
ids = new HashMap<>();
this.element = element;
literalPath = refPath + "->" + element.getName();
workingLang = validationLanguage;
}
public String addToLiteralPath(String... path) {
StringBuilder b = new StringBuilder();
b.append(getLiteralPath());
for (String p : path) {
if (p.startsWith(":")) {
b.append("[");
b.append(p.substring(1));
b.append("]");
} else {
b.append(".");
b.append(p);
}
}
return b.toString();
}
private ElementDefinition getDefinition() {
return definition;
}
public Element getElement() {
return element;
}
public String getLiteralPath() {
return literalPath == null ? "" : literalPath;
}
public List<String> getLogicalPaths() {
return logicalPaths == null ? new ArrayList<String>() : logicalPaths;
}
private ElementDefinition getType() {
return type;
}
public NodeStack pushTarget(Element element, int count, ElementDefinition definition, ElementDefinition type) {
return pushInternal(element, count, definition, type, "->");
}
public NodeStack push(Element element, int count, ElementDefinition definition, ElementDefinition type) {
return pushInternal(element, count, definition, type, ".");
}
private NodeStack pushInternal(Element element, int count, ElementDefinition definition, ElementDefinition type, String sep) {
NodeStack res = new NodeStack(context);
res.ids = ids;
res.parent = this;
res.workingLang = this.workingLang;
res.element = element;
res.definition = definition;
res.literalPath = getLiteralPath() + sep + element.getName();
if (count > -1)
res.literalPath = res.literalPath + "[" + Integer.toString(count) + "]";
else if (element.getSpecial() == null && element.getProperty().isList())
res.literalPath = res.literalPath + "[0]";
else if (element.getProperty().isChoice()) {
String n = res.literalPath.substring(res.literalPath.lastIndexOf(".") + 1);
String en = element.getProperty().getName();
en = en.substring(0, en.length() - 3);
String t = n.substring(en.length());
if (isPrimitiveType(Utilities.uncapitalize(t)))
t = Utilities.uncapitalize(t);
res.literalPath = res.literalPath.substring(0, res.literalPath.lastIndexOf(".")) + "." + en + ".ofType(" + t + ")";
}
res.logicalPaths = new ArrayList<String>();
if (type != null) {
// type will be bull if we on a stitching point of a contained resource, or if....
res.type = type;
String tn = res.type.getPath();
String t = tail(definition.getPath());
if ("Resource".equals(tn)) {
tn = element.fhirType();
}
for (String lp : getLogicalPaths()) {
res.logicalPaths.add(lp + "." + t);
if (t.endsWith("[x]"))
res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + type.getPath());
}
res.logicalPaths.add(tn);
} else if (definition != null) {
for (String lp : getLogicalPaths()) {
res.logicalPaths.add(lp + "." + element.getName());
}
res.logicalPaths.add(definition.typeSummary());
} else
res.logicalPaths.addAll(getLogicalPaths());
return res;
}
private void setType(ElementDefinition type) {
this.type = type;
}
public NodeStack resetIds() {
ids = new HashMap<>();
return this;
}
public Map<String, Element> getIds() {
return ids;
}
private String tail(String path) {
return path.substring(path.lastIndexOf(".") + 1);
}
public boolean isPrimitiveType(String code) {
StructureDefinition sd = context.fetchTypeDefinition(code);
return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
}
public String getWorkingLang() {
return workingLang;
}
public void setWorkingLang(String workingLang) {
this.workingLang = workingLang;
}
public NodeStack getParent() {
return parent;
}
}

View File

@ -9,7 +9,7 @@ public class ResolvedReference {
private Element resource;
private Element focus;
private boolean external;
private InstanceValidator.NodeStack stack;
private NodeStack stack;
public ResolvedReference setResource(Element resource) {
this.resource = resource;
@ -34,12 +34,12 @@ public class ResolvedReference {
return this;
}
public ResolvedReference setStack(InstanceValidator.NodeStack stack) {
public ResolvedReference setStack(NodeStack stack) {
this.stack = stack;
return this;
}
public InstanceValidator.NodeStack getStack() {
public NodeStack getStack() {
return stack;
}

View File

@ -141,6 +141,11 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
else
val.setValidationLanguage(null);
val.setFetcher(this);
if (content.has("packages")) {
for (JsonElement e : content.getAsJsonArray("packages")) {
vCurr.loadIg(e.getAsString(), true);
}
}
if (content.has("questionnaire")) {
String filename = content.get("questionnaire").getAsString();
String contents = TestingUtilities.loadTestResource("validator", filename);
@ -151,6 +156,11 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
String contents = TestingUtilities.loadTestResource("validator", filename);
vCurr.getContext().cacheResource(loadResource(filename, contents));
}
if (content.has("library")) {
String filename = content.get("library").getAsString();
String contents = TestingUtilities.loadTestResource("validator", filename);
vCurr.getContext().cacheResource(loadResource(filename, contents));
}
if (content.has("codesystems")) {
for (JsonElement je : content.getAsJsonArray("codesystems")) {
String filename = je.getAsString();
@ -320,13 +330,11 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
}
if (vm.getLevel() == IssueSeverity.WARNING) {
wc++;
System.out.println("warning: "+vm.getDisplay());
System.out.println(vm.getDisplay());
}
if (vm.getLevel() == IssueSeverity.INFORMATION) {
hc++;
if (java.has("infoCount") || java.has("debug")) {
System.out.println("hint: "+vm.getDisplay());
}
System.out.println(vm.getDisplay());
}
}
if (!TestingUtilities.context(version).isNoTerminologyServer() || !focus.has("tx-dependent")) {