Cross version extennsion support + improve extension validation
This commit is contained in:
parent
4922522adf
commit
3ce532968a
|
@ -381,6 +381,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||||
|
|
||||||
private EnableWhenEvaluator myEnableWhenEvaluator = new EnableWhenEvaluator();
|
private EnableWhenEvaluator myEnableWhenEvaluator = new EnableWhenEvaluator();
|
||||||
private String executionId;
|
private String executionId;
|
||||||
|
private XVerExtensionManager xverManager;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Keeps track of whether a particular profile has been checked or not yet
|
* Keeps track of whether a particular profile has been checked or not yet
|
||||||
|
@ -1551,25 +1552,53 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private StructureDefinition checkExtension(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, Element resource, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack) throws FHIRException, IOException {
|
private StructureDefinition checkExtension(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, Element resource, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack, String extensionUrl) throws FHIRException, IOException {
|
||||||
String url = element.getNamedChildValue("url");
|
String url = element.getNamedChildValue("url");
|
||||||
boolean isModifier = element.getName().equals("modifierExtension");
|
boolean isModifier = element.getName().equals("modifierExtension");
|
||||||
|
|
||||||
long t = System.nanoTime();
|
long t = System.nanoTime();
|
||||||
StructureDefinition ex = context.fetchResource(StructureDefinition.class, url);
|
StructureDefinition ex = Utilities.isAbsoluteUrl(url) ? context.fetchResource(StructureDefinition.class, url) : null;
|
||||||
sdTime = sdTime + (System.nanoTime() - t);
|
sdTime = sdTime + (System.nanoTime() - t);
|
||||||
if (ex == null) {
|
if (ex == null) {
|
||||||
if (!url.startsWith("http://hl7.org/fhir/4.0/StructureDefinition/extension-"))
|
if (xverManager == null) {
|
||||||
if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), "The extension " + url + " is unknown, and not allowed here"))
|
xverManager = new XVerExtensionManager(context);
|
||||||
|
}
|
||||||
|
if (xverManager.matchingUrl(url)) {
|
||||||
|
switch (xverManager.status(url)) {
|
||||||
|
case BadVersion:
|
||||||
|
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, "Extension url '" + url + "' evaluation state is not valid (invalidVersion \""+xverManager.getVersion(url)+"\")");
|
||||||
|
break;
|
||||||
|
case Unknown:
|
||||||
|
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, "Extension url '" + url + "' evaluation state is not valid (unknown Element id \""+xverManager.getElementId(url)+"\")");
|
||||||
|
break;
|
||||||
|
case Invalid:
|
||||||
|
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, "Extension url '" + url + "' evaluation state is not valid (Element id \""+xverManager.getElementId(url)+"\" is valid, but cannot be used in a cross-version paradigm because there has been no changes across the relevant versions)");
|
||||||
|
break;
|
||||||
|
case Valid:
|
||||||
|
ex = xverManager.makeDefinition(url);
|
||||||
|
context.generateSnapshot(ex);
|
||||||
|
context.cacheResource(ex);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, "Extension url '" + url + "' evaluation state illegal");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (extensionUrl != null && !isAbsolute(url)) {
|
||||||
|
if (extensionUrl.equals(profile.getUrl())) {
|
||||||
|
rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", hasExtensionSlice(profile, url), "Sub-extension url '" + url + "' is not defined by the Extension "+profile.getUrl());
|
||||||
|
}
|
||||||
|
} else if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), "The extension " + url + " is unknown, and not allowed here")) {
|
||||||
hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, isKnownExtension(url), "Unknown extension " + url);
|
hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, isKnownExtension(url), "Unknown extension " + url);
|
||||||
} else {
|
}
|
||||||
if (def.getIsModifier())
|
}
|
||||||
|
if (ex != null) {
|
||||||
|
if (def.getIsModifier()) {
|
||||||
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(),
|
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(),
|
||||||
"Extension modifier mismatch: the extension element is labelled as a modifier, but the underlying extension is not");
|
"Extension modifier mismatch: the extension element is labelled as a modifier, but the underlying extension is not");
|
||||||
else
|
} else {
|
||||||
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(),
|
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(),
|
||||||
"Extension modifier mismatch: the extension element is not labelled as a modifier, but the underlying extension is");
|
"Extension modifier mismatch: the extension element is not labelled as a modifier, but the underlying extension is");
|
||||||
|
}
|
||||||
// two questions
|
// two questions
|
||||||
// 1. can this extension be used here?
|
// 1. can this extension be used here?
|
||||||
checkExtensionContext(errors, element, /* path+"[url='"+url+"']", */ ex, stack, ex.getUrl());
|
checkExtensionContext(errors, element, /* path+"[url='"+url+"']", */ ex, stack, ex.getUrl());
|
||||||
|
@ -1590,12 +1619,21 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||||
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", allowedTypes.contains(actualType), "The Extension '" + url + "' definition allows for the types "+allowedTypes.toString()+" but found type "+actualType);
|
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", allowedTypes.contains(actualType), "The Extension '" + url + "' definition allows for the types "+allowedTypes.toString()+" but found type "+actualType);
|
||||||
|
|
||||||
// 3. is the content of the extension valid?
|
// 3. is the content of the extension valid?
|
||||||
validateElement(hostContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false, true);
|
validateElement(hostContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false, true, url);
|
||||||
|
|
||||||
}
|
}
|
||||||
return ex;
|
return ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasExtensionSlice(StructureDefinition profile, String sliceName) {
|
||||||
|
for (ElementDefinition ed : profile.getSnapshot().getElement()) {
|
||||||
|
if (ed.getPath().equals("Extension.extension.url") && ed.hasFixed() && sliceName.equals(ed.getFixed().primitiveValue())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private String getExtensionType(Element element) {
|
private String getExtensionType(Element element) {
|
||||||
for (Element e : element.getChildren()) {
|
for (Element e : element.getChildren()) {
|
||||||
if (e.getName().startsWith("value")) {
|
if (e.getName().startsWith("value")) {
|
||||||
|
@ -3232,7 +3270,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||||
(rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), defn.hasSnapshot(),
|
(rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), defn.hasSnapshot(),
|
||||||
"StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided"))) {
|
"StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided"))) {
|
||||||
// Don't need to validate against the resource if there's a profile because the profile snapshot will include the relevant parts of the resources
|
// Don't need to validate against the resource if there's a profile because the profile snapshot will include the relevant parts of the resources
|
||||||
validateElement(hostContext, errors, defn, defn.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false, true);
|
validateElement(hostContext, errors, defn, defn.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// specific known special validations
|
// specific known special validations
|
||||||
|
@ -3253,7 +3291,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||||
// todo: re-enable this when I can deal with the impact... (GG)
|
// todo: re-enable this when I can deal with the impact... (GG)
|
||||||
// if (!profileUsage.getProfile().getType().equals(resource.fhirType()))
|
// if (!profileUsage.getProfile().getType().equals(resource.fhirType()))
|
||||||
// throw new FHIRException("Profile type mismatch - resource is "+resource.fhirType()+", and profile is for "+profileUsage.getProfile().getType());
|
// throw new FHIRException("Profile type mismatch - resource is "+resource.fhirType()+", and profile is for "+profileUsage.getProfile().getType());
|
||||||
validateElement(hostContext, errors, profileUsage.getProfile(), profileUsage.getProfile().getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false, true);
|
validateElement(hostContext, errors, profileUsage.getProfile(), profileUsage.getProfile().getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false, true, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4287,7 +4325,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
|
||||||
// firstBase = ebase == null ? base : ebase;
|
// firstBase = ebase == null ? base : ebase;
|
||||||
|
|
||||||
private void validateElement(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context,
|
private void validateElement(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context,
|
||||||
Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext) throws FHIRException, FHIRException, IOException {
|
Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, String extensionUrl) throws FHIRException, FHIRException, IOException {
|
||||||
// element.markValidation(profile, definition);
|
// element.markValidation(profile, definition);
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
|
@ -4324,12 +4362,12 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
|
||||||
|
|
||||||
// 5. inspect each child for validity
|
// 5. inspect each child for validity
|
||||||
for (ElementInfo ei : children) {
|
for (ElementInfo ei : children) {
|
||||||
checkChild(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei);
|
checkChild(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkChild(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition,
|
public void checkChild(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition,
|
||||||
Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, ElementInfo ei)
|
Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, ElementInfo ei, String extensionUrl)
|
||||||
throws FHIRException, IOException, DefinitionException {
|
throws FHIRException, IOException, DefinitionException {
|
||||||
List<String> profiles = new ArrayList<String>();
|
List<String> profiles = new ArrayList<String>();
|
||||||
if (ei.definition != null) {
|
if (ei.definition != null) {
|
||||||
|
@ -4405,6 +4443,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
|
||||||
String eiPath = ei.path;
|
String eiPath = ei.path;
|
||||||
assert(eiPath.equals(localStackLiterapPath)) : "ei.path: " + ei.path + " - localStack.getLiteralPath: " + localStackLiterapPath;
|
assert(eiPath.equals(localStackLiterapPath)) : "ei.path: " + ei.path + " - localStack.getLiteralPath: " + localStackLiterapPath;
|
||||||
boolean thisIsCodeableConcept = false;
|
boolean thisIsCodeableConcept = false;
|
||||||
|
String thisExtension = null;
|
||||||
boolean checkDisplay = true;
|
boolean checkDisplay = true;
|
||||||
|
|
||||||
checkInvariants(hostContext, errors, profile, ei.definition, resource, ei.element, localStack, true);
|
checkInvariants(hostContext, errors, profile, ei.definition, resource, ei.element, localStack, true);
|
||||||
|
@ -4431,8 +4470,17 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
|
||||||
} else if (type.equals("Reference")) {
|
} else if (type.equals("Reference")) {
|
||||||
checkReference(hostContext, errors, ei.path, ei.element, profile, ei.definition, actualType, localStack);
|
checkReference(hostContext, errors, ei.path, ei.element, profile, ei.definition, actualType, localStack);
|
||||||
// We only check extensions if we're not in a complex extension or if the element we're dealing with is not defined as part of that complex extension
|
// We only check extensions if we're not in a complex extension or if the element we're dealing with is not defined as part of that complex extension
|
||||||
} else if (type.equals("Extension") && ei.element.getChildValue("url") != null && ei.element.getChildValue("url").contains("/")) {
|
} else if (type.equals("Extension")) {
|
||||||
checkExtension(hostContext, errors, ei.path, resource, ei.element, ei.definition, profile, localStack);
|
Element eurl = ei.element.getNamedChild("url");
|
||||||
|
if (rule(errors, IssueType.INVALID, ei.path, eurl != null, "Extension.url is required")) {
|
||||||
|
String url = eurl.primitiveValue();
|
||||||
|
thisExtension = url;
|
||||||
|
if (rule(errors, IssueType.INVALID, ei.path, !Utilities.noString(url), "Extension.url is required")) {
|
||||||
|
if (rule(errors, IssueType.INVALID, ei.path, (extensionUrl != null) || Utilities.isAbsoluteUrl(url), "Extension.url must be an absolute URL")) {
|
||||||
|
checkExtension(hostContext, errors, ei.path, resource, ei.element, ei.definition, profile, localStack, extensionUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} 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
|
||||||
// (str.matches(".*([.,/])work\\1$"))
|
// (str.matches(".*([.,/])work\\1$"))
|
||||||
|
@ -4450,7 +4498,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), ei.definition != null, "Unrecognised Content " + ei.name))
|
if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), ei.definition != null, "Unrecognised Content " + ei.name))
|
||||||
validateElement(hostContext, errors, profile, ei.definition, null, null, resource, ei.element, type, localStack, false, true);
|
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;
|
boolean elementValidated = false;
|
||||||
|
@ -4490,7 +4538,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
|
||||||
p = this.context.fetchResource(StructureDefinition.class, typeProfile);
|
p = this.context.fetchResource(StructureDefinition.class, typeProfile);
|
||||||
if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown profile " + typeProfile)) {
|
if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown profile " + typeProfile)) {
|
||||||
List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>();
|
List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>();
|
||||||
validateElement(hostContext, profileErrors, p, getElementByTail(p, tail), profile, ei.definition, resource, ei.element, type, localStack, thisIsCodeableConcept, checkDisplay);
|
validateElement(hostContext, profileErrors, p, getElementByTail(p, tail), profile, ei.definition, resource, ei.element, type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension);
|
||||||
if (hasErrors(profileErrors))
|
if (hasErrors(profileErrors))
|
||||||
badProfiles.put(typeProfile, profileErrors);
|
badProfiles.put(typeProfile, profileErrors);
|
||||||
else
|
else
|
||||||
|
@ -4522,15 +4570,15 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
|
||||||
if (p!=null) {
|
if (p!=null) {
|
||||||
if (!elementValidated) {
|
if (!elementValidated) {
|
||||||
if (ei.element.getSpecial() == SpecialElement.BUNDLE_ENTRY || ei.element.getSpecial() == SpecialElement.BUNDLE_OUTCOME || ei.element.getSpecial() == SpecialElement.PARAMETER )
|
if (ei.element.getSpecial() == SpecialElement.BUNDLE_ENTRY || ei.element.getSpecial() == SpecialElement.BUNDLE_OUTCOME || ei.element.getSpecial() == SpecialElement.PARAMETER )
|
||||||
validateElement(hostContext, errors, p, getElementByTail(p, tail), profile, ei.definition, ei.element, ei.element, type, localStack, thisIsCodeableConcept, checkDisplay);
|
validateElement(hostContext, errors, p, getElementByTail(p, tail), profile, ei.definition, ei.element, ei.element, type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension);
|
||||||
else
|
else
|
||||||
validateElement(hostContext, errors, p, getElementByTail(p, tail), profile, ei.definition, resource, ei.element, type, localStack, thisIsCodeableConcept, checkDisplay);
|
validateElement(hostContext, errors, p, getElementByTail(p, tail), profile, ei.definition, resource, ei.element, type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension);
|
||||||
}
|
}
|
||||||
int index = profile.getSnapshot().getElement().indexOf(ei.definition);
|
int index = profile.getSnapshot().getElement().indexOf(ei.definition);
|
||||||
if (index < profile.getSnapshot().getElement().size() - 1) {
|
if (index < profile.getSnapshot().getElement().size() - 1) {
|
||||||
String nextPath = profile.getSnapshot().getElement().get(index+1).getPath();
|
String nextPath = profile.getSnapshot().getElement().get(index+1).getPath();
|
||||||
if (!nextPath.equals(ei.definition.getPath()) && nextPath.startsWith(ei.definition.getPath()))
|
if (!nextPath.equals(ei.definition.getPath()) && nextPath.startsWith(ei.definition.getPath()))
|
||||||
validateElement(hostContext, errors, profile, ei.definition, null, null, resource, ei.element, type, localStack, thisIsCodeableConcept, checkDisplay);
|
validateElement(hostContext, errors, profile, ei.definition, null, null, resource, ei.element, type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -406,6 +406,8 @@ public class ValidationEngine implements IValidatorResourceFetcher {
|
||||||
context = SimpleWorkerContext.fromDefinitions(source, loaderForVersion());
|
context = SimpleWorkerContext.fromDefinitions(source, loaderForVersion());
|
||||||
context.setAllowLoadingDuplicates(true); // because of Forge
|
context.setAllowLoadingDuplicates(true); // because of Forge
|
||||||
context.setExpansionProfile(makeExpProfile());
|
context.setExpansionProfile(makeExpProfile());
|
||||||
|
NpmPackage npm = pcm.loadPackage("hl7.fhir.xver-extensions", "0.0.1");
|
||||||
|
context.loadFromPackage(npm, null);
|
||||||
grabNatives(source, "http://hl7.org/fhir");
|
grabNatives(source, "http://hl7.org/fhir");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1614,7 +1616,8 @@ public class ValidationEngine implements IValidatorResourceFetcher {
|
||||||
if (context.fetchResource(Resource.class, url) != null)
|
if (context.fetchResource(Resource.class, url) != null)
|
||||||
return true;
|
return true;
|
||||||
if (Utilities.existsInList(url, "http://hl7.org/fhir/sid/us-ssn", "http://hl7.org/fhir/sid/cvx", "http://hl7.org/fhir/sid/ndc", "http://hl7.org/fhir/sid/us-npi",
|
if (Utilities.existsInList(url, "http://hl7.org/fhir/sid/us-ssn", "http://hl7.org/fhir/sid/cvx", "http://hl7.org/fhir/sid/ndc", "http://hl7.org/fhir/sid/us-npi",
|
||||||
"http://hl7.org/fhir/sid/icd-10-vn", "http://hl7.org/fhir/sid/icd-10-cm", "http://hl7.org/fhir/sid/icd-9-cm", "http://hl7.org/fhir/w5", "http://hl7.org/fhir/fivews", "http://hl7.org/fhir/workflow")) {
|
"http://hl7.org/fhir/sid/icd-10-vn", "http://hl7.org/fhir/sid/icd-10-cm", "http://hl7.org/fhir/sid/icd-9-cm", "http://hl7.org/fhir/w5", "http://hl7.org/fhir/fivews",
|
||||||
|
"http://hl7.org/fhir/workflow", "http://hl7.org/fhir/ConsentPolicy/opt-out", "http://hl7.org/fhir/ConsentPolicy/opt-in")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
package org.hl7.fhir.r5.validation;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hl7.fhir.exceptions.FHIRException;
|
||||||
|
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||||
|
import org.hl7.fhir.r5.model.ElementDefinition;
|
||||||
|
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
|
||||||
|
import org.hl7.fhir.r5.model.Enumerations.FHIRVersion;
|
||||||
|
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
|
||||||
|
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||||
|
import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType;
|
||||||
|
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
|
||||||
|
import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
|
||||||
|
import org.hl7.fhir.r5.model.UriType;
|
||||||
|
import org.hl7.fhir.r5.validation.XVerExtensionManager.XVerExtensionStatus;
|
||||||
|
import org.hl7.fhir.utilities.json.JsonTrackingParser;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
public class XVerExtensionManager {
|
||||||
|
|
||||||
|
public enum XVerExtensionStatus {
|
||||||
|
BadVersion, Unknown, Invalid, Valid
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, JsonObject> lists = new HashMap<>();
|
||||||
|
private IWorkerContext context;
|
||||||
|
|
||||||
|
public XVerExtensionManager(IWorkerContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XVerExtensionStatus status(String url) throws IOException {
|
||||||
|
String v = url.substring(20, 23);
|
||||||
|
String e = url.substring(54);
|
||||||
|
if (!lists.containsKey(v)) {
|
||||||
|
if (context.getBinaries().containsKey("xver-paths-"+v+".json")) {
|
||||||
|
lists.put(v, JsonTrackingParser.parseJson(context.getBinaries().get("xver-paths-"+v+".json")));
|
||||||
|
} else {
|
||||||
|
return XVerExtensionStatus.BadVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JsonObject root = lists.get(v);
|
||||||
|
JsonObject path = root.getAsJsonObject(e);
|
||||||
|
if (path == null) {
|
||||||
|
return XVerExtensionStatus.Unknown;
|
||||||
|
}
|
||||||
|
if (path.has("elements") || path.has("types")) {
|
||||||
|
return XVerExtensionStatus.Valid;
|
||||||
|
} else {
|
||||||
|
return XVerExtensionStatus.Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getElementId(String url) {
|
||||||
|
return url.substring(54);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StructureDefinition makeDefinition(String url) {
|
||||||
|
String v = url.substring(20, 23);
|
||||||
|
String e = url.substring(54);
|
||||||
|
JsonObject root = lists.get(v);
|
||||||
|
JsonObject path = root.getAsJsonObject(e);
|
||||||
|
|
||||||
|
StructureDefinition sd = new StructureDefinition();
|
||||||
|
sd.setUrl(url);
|
||||||
|
sd.setVersion(context.getVersion());
|
||||||
|
sd.setFhirVersion(FHIRVersion.fromCode(context.getVersion()));
|
||||||
|
sd.setKind(StructureDefinitionKind.COMPLEXTYPE);
|
||||||
|
sd.setType("Extension");
|
||||||
|
sd.setDerivation(TypeDerivationRule.CONSTRAINT);
|
||||||
|
sd.setName("Extension-"+v+"-"+e);
|
||||||
|
sd.setTitle("Extension Definition for "+e+" for Version "+v);
|
||||||
|
sd.setStatus(PublicationStatus.ACTIVE);
|
||||||
|
sd.setExperimental(false);
|
||||||
|
sd.setDate(new Date());
|
||||||
|
sd.setPublisher("FHIR Project");
|
||||||
|
sd.setPurpose("Defined so the validator can validate cross version extensions (see http://hl7.org/fhir/versions.html#extensions)");
|
||||||
|
sd.setAbstract(false);
|
||||||
|
sd.addContext().setType(ExtensionContextType.ELEMENT).setExpression(head(e));
|
||||||
|
sd.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/Extension");
|
||||||
|
if (path.has("types")) {
|
||||||
|
sd.getDifferential().addElement().setPath("Extension.extension").setMax("0");
|
||||||
|
sd.getDifferential().addElement().setPath("Extension.url").setFixed(new UriType(url));
|
||||||
|
ElementDefinition val = sd.getDifferential().addElement().setPath("Extension.value[x]").setMin(1);
|
||||||
|
populateTypes(path, val);
|
||||||
|
} else if (path.has("elements")) {
|
||||||
|
for (JsonElement i : path.getAsJsonArray("elements")) {
|
||||||
|
String s = i.getAsString();
|
||||||
|
sd.getDifferential().addElement().setPath("Extension.extension").setSliceName(s);
|
||||||
|
sd.getDifferential().addElement().setPath("Extension.extension.extension").setMax("0");
|
||||||
|
sd.getDifferential().addElement().setPath("Extension.extension.url").setFixed(new UriType(s));
|
||||||
|
ElementDefinition val = sd.getDifferential().addElement().setPath("Extension.extension.value[x]").setMin(1);
|
||||||
|
JsonObject elt = root.getAsJsonObject(e+"."+s);
|
||||||
|
if (!elt.has("types")) {
|
||||||
|
throw new FHIRException("Internal error - nested elements not supported yet");
|
||||||
|
}
|
||||||
|
populateTypes(elt, val);
|
||||||
|
}
|
||||||
|
sd.getDifferential().addElement().setPath("Extension.url").setFixed(new UriType(url));
|
||||||
|
sd.getDifferential().addElement().setPath("Extension.value[x]").setMax("0");
|
||||||
|
} else {
|
||||||
|
throw new FHIRException("Internal error - attempt to define extension for "+url+" when it is invalid");
|
||||||
|
}
|
||||||
|
return sd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void populateTypes(JsonObject path, ElementDefinition val) {
|
||||||
|
for (JsonElement i : path.getAsJsonArray("types")) {
|
||||||
|
String s = i.getAsString();
|
||||||
|
if (s.contains("(")) {
|
||||||
|
String t = s.substring(0, s.indexOf("("));
|
||||||
|
TypeRefComponent tr = val.addType().setCode(s);
|
||||||
|
s = s.substring(t.length()+1);
|
||||||
|
for (String p : s.substring(0, s.length()-1).split("\\|")) {
|
||||||
|
tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/"+p);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val.addType().setCode(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String head(String id) {
|
||||||
|
if (id.contains(".")) {
|
||||||
|
return id.substring(0, id.lastIndexOf("."));
|
||||||
|
} else {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion(String url) {
|
||||||
|
return url.substring(20, 23);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matchingUrl(String url) {
|
||||||
|
if (url == null || url.length() < 56) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String pfx = url.substring(0, 20);
|
||||||
|
String v = url.substring(20, 23);
|
||||||
|
String sfx = url.substring(23, 54);
|
||||||
|
return pfx.equals("http://hl7.org/fhir/") &&
|
||||||
|
isVersionPattern(v) && sfx.equals("/StructureDefinition/extension-");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isVersionPattern(String v) {
|
||||||
|
return v.length() == 3 && Character.isDigit(v.charAt(0)) && v.charAt(1) == '.' && Character.isDigit(v.charAt(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue