fixes to snapshot generation and validation for Bundle.tnry slicing by resource profile

This commit is contained in:
Grahame Grieve 2020-01-15 17:55:24 +11:00
parent 9f1697dad1
commit c13de56203
2 changed files with 173 additions and 31 deletions

View File

@ -248,6 +248,8 @@ public class ProfileUtilities extends TranslatingUtilities {
public static final String IS_DERIVED = "derived.fact"; public static final String IS_DERIVED = "derived.fact";
public static final String UD_ERROR_STATUS = "error-status"; public static final String UD_ERROR_STATUS = "error-status";
private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed"; private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed";
private static final boolean COPY_BINDING_EXTENSIONS = false;
private static final boolean DONT_DO_THIS = false;
private final boolean ADD_REFERENCE_TO_TABLE = true; private final boolean ADD_REFERENCE_TO_TABLE = true;
private boolean useTableForFixedValues = true; private boolean useTableForFixedValues = true;
@ -574,6 +576,34 @@ public class ProfileUtilities extends TranslatingUtilities {
} }
} }
} }
// last, check for wrong profiles or target profiles
for (ElementDefinition ed : derived.getSnapshot().getElement()) {
for (TypeRefComponent t : ed.getType()) {
for (UriType u : t.getProfile()) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue());
if (sd == null) {
if (messages != null) {
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING));
}
} else {
String wt = t.getWorkingCode();
if (ed.getPath().equals("Bundle.entry.response.outcome")) {
wt = "OperationOutcome";
}
if (!sd.getType().equals(wt)) {
boolean ok = isCompatibleType(wt, sd.getType());
if (!ok) {
String smsg = "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt;
if (exception)
throw new DefinitionException(smsg);
else
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), smsg, IssueSeverity.ERROR));
}
}
}
}
}
}
} catch (Exception e) { } catch (Exception e) {
// if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind
derived.setSnapshot(null); derived.setSnapshot(null);
@ -581,6 +611,18 @@ public class ProfileUtilities extends TranslatingUtilities {
} }
} }
private boolean isCompatibleType(String base, String type) {
StructureDefinition sd = context.fetchTypeDefinition(type);
while (sd != null) {
if (sd.getType().equals(base)) {
return true;
}
sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
}
return false;
}
private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) { private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) {
StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent(); StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent();
for (ElementDefinition sed : source.getElement()) { for (ElementDefinition sed : source.getElement()) {
@ -2293,10 +2335,25 @@ public class ProfileUtilities extends TranslatingUtilities {
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Unable to check if "+derived.getBinding().getValueSet()+" is a proper subset of " +base.getBinding().getValueSet()+" - base value set is too large to check", ValidationMessage.IssueSeverity.WARNING)); messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Unable to check if "+derived.getBinding().getValueSet()+" is a proper subset of " +base.getBinding().getValueSet()+" - base value set is too large to check", ValidationMessage.IssueSeverity.WARNING));
else if (!isSubset(expBase.getValueset(), expDerived.getValueset())) else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet(), ValidationMessage.IssueSeverity.ERROR)); messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet(), ValidationMessage.IssueSeverity.ERROR));
} }
} }
base.setBinding(derived.getBinding().copy()); ElementDefinitionBindingComponent d = derived.getBinding();
ElementDefinitionBindingComponent nb = base.getBinding().copy();
if (!COPY_BINDING_EXTENSIONS) {
nb.getExtension().clear();
}
nb.setDescription(null);
nb.getExtension().addAll(d.getExtension());
if (d.hasStrength()) {
nb.setStrength(d.getStrength());
}
if (d.hasDescription()) {
nb.setDescription(d.getDescription());
}
if (d.hasValueSet()) {
nb.setValueSet(d.getValueSet());
}
base.setBinding(nb);
} else if (trimDifferential) } else if (trimDifferential)
derived.setBinding(null); derived.setBinding(null);
else else
@ -2316,31 +2373,61 @@ public class ProfileUtilities extends TranslatingUtilities {
} }
if (derived.hasType()) { if (derived.hasType()) {
// if (derived.getPath().endsWith("[x]") && derived.hasSlicing() && derived.getSlicing().getRules() != SlicingRules.CLOSED) { if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
// // if we're slicing, and not closed, then it's the same list
//
// derived.getType().clear();
// derived.getType().addAll(base.getType());
// } else
if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
if (base.hasType()) { if (base.hasType()) {
for (TypeRefComponent ts : derived.getType()) { for (TypeRefComponent ts : derived.getType()) {
// if (!ts.hasCode()) { // ommitted in the differential; copy it over....
// if (base.getType().size() > 1)
// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": constrained type code must be present if there are multiple types ("+base.typeSummary()+")");
// if (base.getType().get(0).getCode() != null)
// ts.setCode(base.getType().get(0).getCode());
// }
boolean ok = false; boolean ok = false;
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
String t = ts.getWorkingCode(); String t = ts.getWorkingCode();
for (TypeRefComponent td : base.getType()) {; for (TypeRefComponent td : base.getType()) {;
String tt = td.getWorkingCode(); String tt = td.getWorkingCode();
b.append(tt); b.append(tt);
if (td.hasCode() && (tt.equals(t) || "Extension".equals(tt) || (t.equals("uri") && tt.equals("string")) || // work around for old badly generated SDs if (td.hasCode() && (tt.equals(t))) {
"Element".equals(tt) || "*".equals(tt) ||
(("Resource".equals(tt) || ("DomainResource".equals(tt)) && pkp.isResource(t)))))
ok = true; ok = true;
}
if (!ok) {
StructureDefinition sdt = context.fetchTypeDefinition(tt);
if (sdt != null && sdt.getAbstract()) {
StructureDefinition sdb = context.fetchTypeDefinition(t);
while (sdb != null && !ok) {
ok = sdb.getType().equals(sdt.getUrl());
sdb = context.fetchResource(StructureDefinition.class, sdb.getBaseDefinition());
}
}
}
// work around for old badly generated SDs
if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) {
ok = true;
}
if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) {
ok = true;
}
if (ok && ts.hasTargetProfile()) {
// check that any derived target has a reference chain back to one of the base target profiles
for (UriType u : ts.getTargetProfile()) {
boolean tgtOk = false;
String url = u.getValue();
while (url != null && !td.hasTargetProfile(url)) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
if (sd == null) {
if (messages != null) {
messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, purl+"#"+derived.getPath(), "Connect check whether the target profile "+url+" is valid constraint on the base because it is not known", IssueSeverity.WARNING));
}
url = null;
tgtOk = true; // suppress error message
} else {
url = sd.getBaseDefinition();
}
}
if (!tgtOk) {
if (messages == null) {
throw new FHIRException("Error at "+purl+"#"+derived.getPath()+": The target profile "+url+" is not valid constraint on the base ("+td.getTargetProfile()+")");
} else {
messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "The target profile "+url+" is not valid constraint on the base ("+td.getTargetProfile()+")", IssueSeverity.ERROR));
}
}
}
}
} }
if (!ok) if (!ok)
throw new DefinitionException("StructureDefinition "+purl+" at "+derived.getPath()+": illegal constrained type "+t+" from "+b.toString()+" in "+srcSD.getUrl()); throw new DefinitionException("StructureDefinition "+purl+" at "+derived.getPath()+": illegal constrained type "+t+" from "+b.toString()+" in "+srcSD.getUrl());

View File

@ -4287,19 +4287,73 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
private void validateContains(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, ElementDefinition child, ElementDefinition context, Element resource, Element element, NodeStack stack, IdStatus idstatus) throws FHIRException, FHIRException, IOException { private void validateContains(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, ElementDefinition child, ElementDefinition context, Element resource, Element element, NodeStack stack, IdStatus idstatus) throws FHIRException, FHIRException, IOException {
String resourceName = element.getType(); String resourceName = element.getType();
long t = System.nanoTime(); TypeRefComponent trr = null;
StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName); for (TypeRefComponent tr : child.getType()) {
sdTime = sdTime + (System.nanoTime() - t); if (tr.getCode().equals("Resource")) {
// special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise trr = tr;
ValidatorHostContext hc = null; break;
if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY || element.getSpecial() == SpecialElement.BUNDLE_OUTCOME || element.getSpecial() == SpecialElement.PARAMETER ) { }
resource = element;
hc = hostContext.forEntry(element);
} else {
hc = hostContext.forContained(element);
} }
if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, "No profile found for contained resource of type '" + resourceName + "'")) if (trr == null) {
validateResource(hc, errors, resource, element, profile, null, idstatus, stack, false); rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), false, "The type '"+resourceName+" is not valid - no resources allowed here");
} else if (isValidResourceType(resourceName, trr)) {
long t = System.nanoTime();
StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName);
sdTime = sdTime + (System.nanoTime() - t);
// special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise
ValidatorHostContext hc = null;
if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY || element.getSpecial() == SpecialElement.BUNDLE_OUTCOME || element.getSpecial() == SpecialElement.PARAMETER ) {
resource = element;
hc = hostContext.forEntry(element);
} else {
hc = hostContext.forContained(element);
}
if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, "No profile found for contained resource of type '" + resourceName + "'")) {
validateResource(hc, errors, resource, element, profile, null, idstatus, stack, false);
}
} else {
List<String> types = new ArrayList<>();
for (UriType u : trr.getProfile()) {
StructureDefinition sd = this.context.fetchResource(StructureDefinition.class, u.getValue());
if (sd != null && !types.contains(sd.getType())) {
types.add(sd.getType());
}
}
if (types.size() == 1) {
rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), false, "The type '"+resourceName+"' is not valid - must be "+types.get(0));
} else {
rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), false, "The type '"+resourceName+"' is not valid - must be one of "+types);
}
}
}
private boolean isValidResourceType(String type, TypeRefComponent def) {
if (!def.hasProfile()) {
return true;
}
List<StructureDefinition> list = new ArrayList<>();
for (UriType u : def.getProfile()) {
StructureDefinition sdt = context.fetchResource(StructureDefinition.class, u.getValue());
if (sdt != null) {
list.add(sdt);
}
}
StructureDefinition sdt = context.fetchTypeDefinition(type);
while (sdt != null) {
if (def.getWorkingCode().equals("Resource")) {
for (StructureDefinition sd : list) {
if (sd.getUrl().equals(sdt.getUrl())) {
return true;
}
if (sd.getType().equals(sdt.getType())) {
return true;
}
}
}
sdt = context.fetchResource(StructureDefinition.class, sdt.getBaseDefinition());
}
return false;
} }
private void validateDocument(List<ValidationMessage> errors, List<Element> entries, Element composition, NodeStack stack, String fullUrl, String id) { private void validateDocument(List<ValidationMessage> errors, List<Element> entries, Element composition, NodeStack stack, String fullUrl, String id) {
@ -4480,6 +4534,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
checkInvariants(hostContext, errors, profile, ei.definition, resource, ei.element, localStack, true); checkInvariants(hostContext, errors, profile, ei.definition, resource, ei.element, localStack, true);
ei.element.markValidation(profile, ei.definition); ei.element.markValidation(profile, ei.definition);
boolean elementValidated = false;
if (type != null) { if (type != null) {
if (isPrimitiveType(type)) { if (isPrimitiveType(type)) {
checkPrimitive(hostContext, errors, ei.path, type, ei.definition, ei.element, profile, stack); checkPrimitive(hostContext, errors, ei.path, type, ei.definition, ei.element, profile, stack);
@ -4514,6 +4569,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
} }
} else if (type.equals("Resource")) { } else if (type.equals("Resource")) {
validateContains(hostContext, errors, ei.path, ei.definition, definition, resource, ei.element, localStack, idStatusForEntry(element, ei)); // if validateContains(hostContext, errors, ei.path, ei.definition, definition, resource, ei.element, localStack, idStatusForEntry(element, ei)); // if
elementValidated = true;
// (str.matches(".*([.,/])work\\1$")) // (str.matches(".*([.,/])work\\1$"))
} else if (Utilities.isAbsoluteUrl(type)) { } else if (Utilities.isAbsoluteUrl(type)) {
StructureDefinition defn = context.fetchTypeDefinition(type); StructureDefinition defn = context.fetchTypeDefinition(type);
@ -4532,7 +4588,6 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
validateElement(hostContext, errors, profile, ei.definition, null, null, resource, ei.element, type, localStack, false, true, null); validateElement(hostContext, errors, profile, ei.definition, null, null, resource, ei.element, type, localStack, false, true, null);
} }
StructureDefinition p = null; StructureDefinition p = null;
boolean elementValidated = false;
String tail = null; String tail = null;
if (profiles.isEmpty()) { if (profiles.isEmpty()) {
if (type != null) { if (type != null) {