Gg 202110 misc validation (#617)

* NPE fixes

* Smart Health Cards support in validator

* Fix bug generating spreadsheets due to sheet name length limitations

* Implement descendent-of filter

* more NPE fixes

* add Element.removeChild

* fix issue generation snapshot and content reference, and work around old erroneous binding description in R4

* improve SHC validation error

* fix for NPE generating ConceptMap spreadsheet

* fix crash in IG publisher rendering illegal content

* Improve slicing error messages

* more improving error message resolving slicing

* add missing code + track prohibited / required elements (improve rendering of IGs)

* fix for broken links in R4B IGs

* fix bug related to logger in FHIRToolingClient

* fix bug related to logger in context

* enable detection of whether tx server knows about value set and better track returned errors from tx server

* make likely source URL visible outside ProfileUtilities

* fix renderers - don't make nonvalid URLs into html links + fix NPE +

* fix bug with sheetnames generating spreadsheets

* supper branches in current version of packages

* report slicing information automatically where slicing is based on profile + fix shc support + support codesystem-properties-mode + fix value set validation on profiles + fix wrong entry point on vaildating contained resources with profiles

* fix misleading validation message + add -ips parameter for validator

* hint not warning when it's an example questionnaire
This commit is contained in:
Grahame Grieve 2021-10-11 08:37:02 +11:00 committed by GitHub
parent df724155e0
commit 237897965b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 343 additions and 149 deletions

View File

@ -126,7 +126,9 @@ public class VersionConvertorContext<T> {
*/
public T getVersionConvertor() {
T result = threadLocalVersionConverter.get();
logger.debug(result.toString());
if (result != null && logger != null) {
logger.debug(result.toString());
}
return result;
}
}

View File

@ -516,7 +516,7 @@ public class FHIRToolingClient {
return p_out;
}
} catch (Exception e) {
handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
handleException("Error performing tx2 operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
}
return null;
}

View File

@ -488,39 +488,39 @@ public class FHIRToolingClient {
// }
public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) {
boolean complex = false;
for (ParametersParameterComponent p : params.getParameter())
complex = complex || !(p.getValue() instanceof PrimitiveType);
Parameters searchResults = null;
String ps = "";
try {
if (!complex)
for (ParametersParameterComponent p : params.getParameter())
if (p.getValue() instanceof PrimitiveType)
ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue())+"&";
ResourceRequest<T> result;
if (complex)
result = utils.issuePostRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), TIMEOUT_OPERATION_LONG);
else
result = utils.issueGetResourceRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), getPreferredResourceFormat(), TIMEOUT_OPERATION_LONG);
result.addErrorStatus(410);//gone
result.addErrorStatus(404);//unknown
result.addSuccessStatus(200);//Only one for now
if(result.isUnsuccessfulRequest())
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
if (result.getPayload() instanceof Parameters)
return (Parameters) result.getPayload();
else {
Parameters p_out = new Parameters();
p_out.addParameter().setName("return").setResource(result.getPayload());
return p_out;
}
} catch (Exception e) {
handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
}
return null;
}
public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) {
boolean complex = false;
for (ParametersParameterComponent p : params.getParameter())
complex = complex || !(p.getValue() instanceof PrimitiveType);
Parameters searchResults = null;
String ps = "";
try {
if (!complex)
for (ParametersParameterComponent p : params.getParameter())
if (p.getValue() instanceof PrimitiveType)
ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue())+"&";
ResourceRequest<T> result;
if (complex)
result = utils.issuePostRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), utils.getResourceAsByteArray(params, false, isJson(getPreferredResourceFormat())), getPreferredResourceFormat(), TIMEOUT_OPERATION_LONG);
else
result = utils.issueGetResourceRequest(resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps), getPreferredResourceFormat(), TIMEOUT_OPERATION_LONG);
result.addErrorStatus(410);//gone
result.addErrorStatus(404);//unknown
result.addSuccessStatus(200);//Only one for now
if(result.isUnsuccessfulRequest())
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome)result.getPayload());
if (result.getPayload() instanceof Parameters)
return (Parameters) result.getPayload();
else {
Parameters p_out = new Parameters();
p_out.addParameter().setName("return").setResource(result.getPayload());
return p_out;
}
} catch (Exception e) {
handleException("Error performing 2b operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
}
return null;
}
public Bundle transaction(Bundle batch) {

View File

@ -277,11 +277,15 @@ public class FHIRToolingClient {
URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
if (complex) {
byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()));
client.getLogger().logRequest("POST", url.toString(), null, body);
if (client.getLogger() != null) {
client.getLogger().logRequest("POST", url.toString(), null, body);
}
result = client.issuePostRequest(url, body, getPreferredResourceFormat(),
"POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
} else {
client.getLogger().logRequest("GET", url.toString(), null, null);
if (client.getLogger() != null) {
client.getLogger().logRequest("GET", url.toString(), null, null);
}
result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
}
if (result.isUnsuccessfulRequest()) {
@ -295,7 +299,7 @@ public class FHIRToolingClient {
return p_out;
}
} catch (Exception e) {
handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
handleException("Error performing tx3 operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
}
return null;
}

View File

@ -267,32 +267,36 @@ public class FHIRToolingClient {
String ps = "";
try {
if (!complex)
for (ParametersParameterComponent p : params.getParameter())
if (p.getValue() instanceof PrimitiveType)
ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue()) + "&";
ResourceRequest<T> result;
URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
if (complex) {
byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()));
client.getLogger().logRequest("POST", url.toString(), null, body);
result = client.issuePostRequest(url, body, getPreferredResourceFormat(),
"POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
} else {
client.getLogger().logRequest("GET", url.toString(), null, null);
result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
if (result.getPayload() instanceof Parameters) {
return (Parameters) result.getPayload();
} else {
Parameters p_out = new Parameters();
p_out.addParameter().setName("return").setResource(result.getPayload());
return p_out;
}
for (ParametersParameterComponent p : params.getParameter())
if (p.getValue() instanceof PrimitiveType)
ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue()) + "&";
ResourceRequest<T> result;
URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
if (complex) {
byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()));
if (client.getLogger() != null) {
client.getLogger().logRequest("POST", url.toString(), null, body);
}
result = client.issuePostRequest(url, body, getPreferredResourceFormat(),
"POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
} else {
if (client.getLogger() != null) {
client.getLogger().logRequest("GET", url.toString(), null, null);
}
result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
if (result.getPayload() instanceof Parameters) {
return (Parameters) result.getPayload();
} else {
Parameters p_out = new Parameters();
p_out.addParameter().setName("return").setResource(result.getPayload());
return p_out;
}
} catch (Exception e) {
handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
handleException("Error performing tx4 operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
}
return null;
}

View File

@ -2452,19 +2452,19 @@ public class ProfileUtilities extends TranslatingUtilities {
if (webUrl != null) {
// also, must touch up the markdown
if (element.hasDefinition())
element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl));
element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl, baseSpecUrl()));
if (element.hasComment())
element.setComment(processRelativeUrls(element.getComment(), webUrl));
element.setComment(processRelativeUrls(element.getComment(), webUrl, baseSpecUrl()));
if (element.hasRequirements())
element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl));
element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl, baseSpecUrl()));
if (element.hasMeaningWhenMissing())
element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl));
element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl, baseSpecUrl()));
}
}
return element;
}
private String processRelativeUrls(String markdown, String webUrl) {
public static String processRelativeUrls(String markdown, String webUrl, String basePath) {
StringBuilder b = new StringBuilder();
int i = 0;
while (i < markdown.length()) {
@ -2487,7 +2487,7 @@ public class ProfileUtilities extends TranslatingUtilities {
//
if (isLikelySourceURLReference(url)) {
b.append("](");
b.append(baseSpecUrl());
b.append(basePath);
i = i + 1;
} else {
b.append("](");
@ -2507,11 +2507,13 @@ public class ProfileUtilities extends TranslatingUtilities {
}
private boolean isLikelySourceURLReference(String url) {
public static boolean isLikelySourceURLReference(String url) {
return
url.startsWith("extensibility.html") ||
url.startsWith("terminologies.html") ||
url.startsWith("observation.html") ||
url.startsWith("datatypes.html") ||
url.startsWith("narrative.html") ||
(url.startsWith("extension-") || url.contains(".html")) ||
url.startsWith("resource-definitions.html");
}

View File

@ -906,9 +906,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
pIn.addParameter().setName("implySystem").setValue(new BooleanType(true));
}
setTerminologyOptions(options, pIn);
res = validateOnServer(vs, pIn);
res = validateOnServer(vs, pIn, options);
} catch (Exception e) {
res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()).setTxLink(txLog == null ? null : txLog.getLastId());
res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()).setTxLink(txLog == null ? null : txLog.getLastId()).setErrorClass(TerminologyServiceErrorClass.SERVER_ERROR);
}
if (res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
unsupportedCodeSystems.add(code.getSystem());
@ -967,7 +967,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
Parameters pIn = new Parameters();
pIn.addParameter().setName("codeableConcept").setValue(code);
setTerminologyOptions(options, pIn);
res = validateOnServer(vs, pIn);
res = validateOnServer(vs, pIn, options);
} catch (Exception e) {
res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()).setTxLink(txLog.getLastId());
}
@ -975,7 +975,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
return res;
}
private ValidationResult validateOnServer(ValueSet vs, Parameters pin) throws FHIRException {
private ValidationResult validateOnServer(ValueSet vs, Parameters pin, ValidationOptions options) throws FHIRException {
boolean cache = false;
if (vs != null) {
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
@ -988,6 +988,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
if (vs != null) {
if (isTxCaching && cacheId != null && cached.contains(vs.getUrl()+"|"+vs.getVersion())) {
pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()+(vs.hasVersion() ? "|"+vs.getVersion() : "")));
} else if (options.getVsAsUrl()){
pin.addParameter().setName("url").setValue(new StringType(vs.getUrl()));
} else {
pin.addParameter().setName("valueSet").setResource(vs);
cached.add(vs.getUrl()+"|"+vs.getVersion());

View File

@ -613,6 +613,11 @@ public interface IWorkerContext {
return this;
}
public ValidationResult setErrorClass(TerminologyServiceErrorClass errorClass) {
this.errorClass = errorClass;
return this;
}
public String getTxLink() {
return txLink;
}

View File

@ -634,7 +634,14 @@ public class Element extends Base {
@Override
public String toString() {
return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
if (name.equals(fhirType()) && isResource()) {
return fhirType()+"/"+getIdBase() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
} else if (isResource()) {
return name+"="+fhirType()+"/"+getIdBase()+ "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
} else {
return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
}
}
@Override

View File

@ -62,6 +62,8 @@ public class Manager {
return "txt";
case VBAR:
return "hl7";
case SHC:
return "shc";
}
return null;
}

View File

@ -422,7 +422,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
if (first) first = false; else td.addText(", ");
if (pcv.hasValueCoding()) {
td.addText(pcv.getValueCoding().getCode());
} else if (pcv.hasValueStringType() && Utilities.isAbsoluteUrl(pcv.getValue().primitiveValue())) {
} else if (pcv.hasValueStringType() && Utilities.isAbsoluteUrlLinkable(pcv.getValue().primitiveValue())) {
td.ah(pcv.getValue().primitiveValue()).tx(pcv.getValue().primitiveValue());
} else {
td.addText(pcv.getValue().primitiveValue());

View File

@ -450,8 +450,10 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
x.addText(((Enumeration<?>) e).getValue().toString()); // todo: look up a display name if there is one
return true;
} else if (e instanceof BooleanType) {
if (((BooleanType) e).getValue()) {
if (((BooleanType) e).hasValue()) {
x.addText(name);
x.addText(": ");
x.addText(((BooleanType) e).getValueAsString());
return true;
}
} else if (e instanceof CodeableReference) {

View File

@ -1013,7 +1013,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer {
private void defn(XhtmlNode tbl, String name, String url, Resource res) throws UnsupportedEncodingException, IOException {
if (res != null && res.hasUserData("path")) {
defn(tbl, "Definition", RendererFactory.factory(res, context).display(res), res.getUserString("path"));
} else if (Utilities.isAbsoluteUrl(url)) {
} else if (Utilities.isAbsoluteUrlLinkable(url)) {
defn(tbl, "Definition", url, url);
} {
defn(tbl, "Definition", url);

View File

@ -84,7 +84,7 @@ public class SpreadsheetGenerator {
if (name.length() > MAX_SENSITIVE_SHEET_NAME_LEN - 2) {
name = name.substring(0, MAX_SENSITIVE_SHEET_NAME_LEN - 2);
}
String s = name;
String s = fixSheetNameChars(name);
if (sheetNames.contains(s)) {
int i = 1;
do {
@ -96,6 +96,26 @@ public class SpreadsheetGenerator {
return wb.createSheet(s);
}
private String fixSheetNameChars(String name) {
StringBuilder b = new StringBuilder();
for (char ch : name.toCharArray()) {
switch (ch) {
case '/':
case '\\':
case '?':
case '*':
case ']':
case '[':
case ':':
b.append('_');
break;
default:
b.append(ch);
}
}
return b.toString();
}
private static Map<String, CellStyle> createStyles(Workbook wb){
Map<String, CellStyle> styles = new HashMap<>();

View File

@ -82,9 +82,9 @@ public class StructureDefinitionSpreadsheetGenerator extends CanonicalSpreadshee
"Slicing Discriminator", "Slicing Description", "Slicing Ordered", "Slicing Rules", "Base Path", "Base Min", "Base Max",
"Condition(s)", "Constraint(s)"};
public StructureDefinitionSpreadsheetGenerator(IWorkerContext context, boolean asXml, boolean hideMustSupportFalse) {
public StructureDefinitionSpreadsheetGenerator(IWorkerContext context, boolean valuesAsXml, boolean hideMustSupportFalse) {
super(context);
this.asXml = asXml;
this.asXml = valuesAsXml;
this.hideMustSupportFalse = hideMustSupportFalse;
}

View File

@ -299,37 +299,40 @@ public class FHIRToolingClient {
String ps = "";
try {
if (!complex)
for (ParametersParameterComponent p : params.getParameter())
if (p.getValue() instanceof PrimitiveType)
ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue()) + "&";
ResourceRequest<T> result;
URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
if (complex) {
byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()));
client.getLogger().logRequest("POST", url.toString(), null, body);
result = client.issuePostRequest(url, body, getPreferredResourceFormat(),
"POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
} else {
client.getLogger().logRequest("GET", url.toString(), null, null);
result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
if (result.getPayload() instanceof Parameters) {
return (Parameters) result.getPayload();
} else {
Parameters p_out = new Parameters();
p_out.addParameter().setName("return").setResource(result.getPayload());
return p_out;
}
for (ParametersParameterComponent p : params.getParameter())
if (p.getValue() instanceof PrimitiveType)
ps += p.getName() + "=" + Utilities.encodeUri(((PrimitiveType) p.getValue()).asStringValue()) + "&";
ResourceRequest<T> result;
URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
if (complex) {
byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()));
if (client.getLogger() != null) {
client.getLogger().logRequest("POST", url.toString(), null, body);
}
result = client.issuePostRequest(url, body, getPreferredResourceFormat(),
"POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
} else {
if (client.getLogger() != null) {
client.getLogger().logRequest("GET", url.toString(), null, null);
}
result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
if (result.getPayload() instanceof Parameters) {
return (Parameters) result.getPayload();
} else {
Parameters p_out = new Parameters();
p_out.addParameter().setName("return").setResource(result.getPayload());
return p_out;
}
} catch (Exception e) {
handleException("Error performing operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
handleException("Error performing tx5 operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
}
return null;
}
public Bundle transaction(Bundle batch) {
Bundle transactionResult = null;
try {

View File

@ -1098,7 +1098,17 @@ public class Utilities {
if (ref != null && ref.contains(":")) {
String scheme = ref.substring(0, ref.indexOf(":"));
String details = ref.substring(ref.indexOf(":")+1);
return (existsInList(scheme, "http", "https", "urn") || isToken(scheme) || Utilities.startsWithInList(ref, "urn:iso:", "urn:iso-iec:", "urn:iso-cie:", "urn:iso-astm:", "urn:iso-ieee:", "urn:iec:"))
return (existsInList(scheme, "http", "https", "urn") || (isToken(scheme) && scheme.equals(scheme.toLowerCase())) || Utilities.startsWithInList(ref, "urn:iso:", "urn:iso-iec:", "urn:iso-cie:", "urn:iso-astm:", "urn:iso-ieee:", "urn:iec:"))
&& details != null && details.length() > 0 && !details.contains(" "); // rfc5141
}
return false;
}
public static boolean isAbsoluteUrlLinkable(String ref) {
if (ref != null && ref.contains(":")) {
String scheme = ref.substring(0, ref.indexOf(":"));
String details = ref.substring(ref.indexOf(":")+1);
return (existsInList(scheme, "http", "https", "ftp"))
&& details != null && details.length() > 0 && !details.contains(" "); // rfc5141
}
return false;

View File

@ -135,7 +135,7 @@ public class VersionUtilities {
}
public static boolean isR5Ver(String ver) {
return ver != null && ver.startsWith(CURRENT_VERSION);
return ver != null && (ver.startsWith(CURRENT_VERSION) || ver.equals("current"));
}
public static boolean isR4BVer(String ver) {

View File

@ -84,7 +84,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
public static final String SECONDARY_SERVER = "https://packages2.fhir.org/packages";
// private static final String SECONDARY_SERVER = "http://local.fhir.org:960/packages";
public static final String PACKAGE_REGEX = "^[a-zA-Z][A-Za-z0-9\\_\\-]*(\\.[A-Za-z0-9\\_\\-]+)+$";
public static final String PACKAGE_VERSION_REGEX = "^[A-Za-z][A-Za-z0-9\\_\\-]*(\\.[A-Za-z0-9\\_\\-]+)+\\#[A-Za-z0-9\\-\\_]+(\\.[A-Za-z0-9\\-\\_]+)*$";
public static final String PACKAGE_VERSION_REGEX = "^[A-Za-z][A-Za-z0-9\\_\\-]*(\\.[A-Za-z0-9\\_\\-]+)+\\#[A-Za-z0-9\\-\\_\\$]+(\\.[A-Za-z0-9\\-\\_\\$]+)*$";
public static final String PACKAGE_VERSION_REGEX_OPT = "^[A-Za-z][A-Za-z0-9\\_\\-]*(\\.[A-Za-z0-9\\_\\-]+)+(\\#[A-Za-z0-9\\-\\_]+(\\.[A-Za-z0-9\\-\\_]+)*)?$";
private static final Logger ourLog = LoggerFactory.getLogger(FilesystemPackageCacheManager.class);
private static final String CACHE_VERSION = "3"; // second version - see wiki page
@ -195,7 +195,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
throw new FHIRException("Cannot add package " + id + " to the package cache - the version '" + version + "' is illegal in this context");
}
for (char ch : version.toCharArray()) {
if (!Character.isAlphabetic(ch) && !Character.isDigit(ch) && !Utilities.existsInList(ch, '.', '-')) {
if (!Character.isAlphabetic(ch) && !Character.isDigit(ch) && !Utilities.existsInList(ch, '.', '-', '$')) {
throw new FHIRException("Cannot add package " + id + " to the package cache - the version '" + version + "' is illegal (ch '" + ch + "'");
}
}
@ -503,9 +503,9 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
// nup, don't have it locally (or it's expired)
FilesystemPackageCacheManager.InputStreamWithSrc source;
if ("current".equals(version)) {
if ("current".equals(version) || version.startsWith("current$")) {
// special case - fetch from ci-build server
source = loadFromCIBuild(id);
source = loadFromCIBuild(id, version.startsWith("current$") ? version.substring(8) : null);
} else {
source = loadFromPackageServer(id, version);
}
@ -524,15 +524,20 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
if (optional)
return null;
else
throw new FHIRException(e.getMessage(), e);
throw new FHIRException("Unable to fetch: "+e.getMessage(), e);
}
}
private InputStreamWithSrc loadFromCIBuild(String id) throws IOException {
private InputStreamWithSrc loadFromCIBuild(String id, String branch) throws IOException {
checkBuildLoaded();
if (ciList.containsKey(id)) {
InputStream stream = fetchFromUrlSpecific(Utilities.pathURL(ciList.get(id), "package.tgz"), false);
return new InputStreamWithSrc(stream, Utilities.pathURL(ciList.get(id), "package.tgz"), "current");
if (branch == null) {
InputStream stream = fetchFromUrlSpecific(Utilities.pathURL(ciList.get(id), "package.tgz"), false);
return new InputStreamWithSrc(stream, Utilities.pathURL(ciList.get(id), "package.tgz"), "current");
} else {
InputStream stream = fetchFromUrlSpecific(Utilities.pathURL(ciList.get(id), "branches", branch, "package.tgz"), false);
return new InputStreamWithSrc(stream, Utilities.pathURL(ciList.get(id), "branches", branch, "package.tgz"), "current$"+branch);
}
} else if (id.startsWith("hl7.fhir.r5")) {
InputStream stream = fetchFromUrlSpecific(Utilities.pathURL("http://build.fhir.org", id + ".tgz"), false);
return new InputStreamWithSrc(stream, Utilities.pathURL("http://build.fhir.org", id + ".tgz"), "current");

View File

@ -514,6 +514,7 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
public String[] sliceText;
private boolean slicingHint;
private boolean signpost;
private boolean criticalSignpost;
/**
@ -772,9 +773,10 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
return sliceHtml;
}
public void setSliceHtml(String sliceHtml, String[] text) {
public ValidationMessage setSliceHtml(String sliceHtml, String[] text) {
this.sliceHtml = sliceHtml;
this.sliceText = text;
return this;
}
public String getMessageId() {
@ -795,5 +797,14 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
return this;
}
public boolean isCriticalSignpost() {
return criticalSignpost;
}
public ValidationMessage setCriticalSignpost(boolean criticalSignpost) {
this.criticalSignpost = criticalSignpost;
return this;
}
}

View File

@ -11,6 +11,7 @@ public class ValidationOptions {
private boolean useClient = true;
private boolean guessSystem = false;
private ValueSetMode valueSetMode = ValueSetMode.ALL_CHECKS;
private boolean vsAsUrl;
public ValidationOptions() {
super();
@ -42,7 +43,8 @@ public class ValidationOptions {
ValidationOptions n = new ValidationOptions(language);
n.useServer = useServer;
n.useClient = useClient;
n.guessSystem = guessSystem;
n.guessSystem = guessSystem;
n.vsAsUrl = vsAsUrl;
return n;
}
@ -96,5 +98,14 @@ public class ValidationOptions {
return valueSetMode;
}
public ValidationOptions setVsAsUrl() {
vsAsUrl = true;
return this;
}
public boolean getVsAsUrl() {
return vsAsUrl;
}
}

View File

@ -229,7 +229,6 @@ Validation_VAL_Profile_SliceOrder = As specified by profile {0}, Element ''{1}''
Validation_VAL_Profile_Unknown = Profile reference ''{0}'' has not been checked because it is unknown
VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY = Profile reference ''{0}'' has not been checked because it is unknown, and the validator is set to not fetch unknown profiles
VALIDATION_VAL_PROFILE_UNKNOWN_ERROR = Profile reference ''{0}'' has not been checked because it is unknown, and fetching it resulted in the error {1}
Validation_VAL_Profile_WrongType = Specified profile type was ''{0}'', but found type ''{1}''
Validation_VAL_Unknown_Profile = Unknown profile {0}
XHTML_XHTML_Attribute_Illegal = Illegal attribute name in the XHTML (''{0}'' on ''{1}'')
XHTML_XHTML_Element_Illegal = Illegal element name in the XHTML (''{0}'')
@ -503,7 +502,7 @@ TYPE_SPECIFIC_CHECKS_DT_ATT_TOO_LONG = Attachment size is {0} bytes which exceed
TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT = Attachments have data and/or url, or else SHOULD have either contentType and/or language
TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG = Base64 size is {0} bytes which exceeds the stated limit of {1} bytes
TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS = Found {0} decimal places which exceeds the stated limit of {1} digits
Validation_VAL_Profile_WrongType = Specified profile type was ''{0}'', but found type ''{1}''
Validation_VAL_Profile_WrongType = Specified profile type was ''{0}'' in profile ''{2}'', but found type ''{1}''
Validation_VAL_Profile_WrongType2 = Type mismatch processing profile {0} at path {1}: The element type is {4}, but the profile {3} is for a different type {2}
VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT = Illegal constraint in profile {0} at path {1} - cannot constrain to type {2} from base types {3}
EXTENSION_EXT_CONTEXT_WRONG_XVER = The extension {0} from FHIR version {3} is not allowed to be used at this point (allowed = {1}; this element is [{2}; this is a warning since contexts may be renamed between FHIR versions)

View File

@ -2,7 +2,11 @@ package org.hl7.fhir.validation;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
/*
@ -262,9 +266,9 @@ public class BaseValidator {
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
//FIXME: formatMessage should be done here
protected boolean slicingHint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg, String html, String[] text) {
protected boolean slicingHint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, boolean isCritical, String msg, String html, String[] text) {
if (!thePass) {
addValidationMessage(errors, type, line, col, path, msg, IssueSeverity.INFORMATION, null).setSlicingHint(true).setSliceHtml(html, text);
addValidationMessage(errors, type, line, col, path, msg, IssueSeverity.INFORMATION, null).setSlicingHint(true).setSliceHtml(html, text).setCriticalSignpost(isCritical);
}
return thePass;
}

View File

@ -139,6 +139,8 @@ public class IgLoader {
res.cntType = Manager.FhirFormat.XML;
else if (t.getKey().endsWith(".ttl"))
res.cntType = Manager.FhirFormat.TURTLE;
else if (t.getKey().endsWith(".shc"))
res.cntType = Manager.FhirFormat.SHC;
else if (t.getKey().endsWith(".txt") || t.getKey().endsWith(".map"))
res.cntType = Manager.FhirFormat.TEXT;
else

View File

@ -71,6 +71,8 @@ import org.hl7.fhir.validation.cli.utils.*;
import java.io.File;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.util.ArrayList;
import java.util.List;
/**
* A executable class that will validate one or more FHIR resources against
@ -101,6 +103,8 @@ public class ValidatorCli {
TimeTracker tt = new TimeTracker();
TimeTracker.Session tts = tt.start("Loading");
args = preProcessArgs(args);
Display.displayVersion();
Display.displaySystemInfo();
@ -160,6 +164,35 @@ public class ValidatorCli {
}
}
private static String[] preProcessArgs(String[] args) {
// ips$branch --> -version 4.0 -ig hl7.fhir.uv.ips#current$connectathon-2 -profile http://hl7.org/fhir/uv/ips/StructureDefinition/Bundle-uv-ips
List<String> res = new ArrayList<>();
for (String a : args) {
if (a.equals("-ips")) {
res.add("-version");
res.add("4.0");
res.add("-ig");
res.add("hl7.fhir.uv.ips#current$connectathon-2");
res.add("-profile");
res.add("http://hl7.org/fhir/uv/ips/StructureDefinition/Bundle-uv-ips");
} else if (a.startsWith("-ips$")) {
res.add("-version");
res.add("4.0");
res.add("-ig");
res.add("hl7.fhir.uv.ips#current$"+a.substring(5));
res.add("-profile");
res.add("http://hl7.org/fhir/uv/ips/StructureDefinition/Bundle-uv-ips");
} else {
res.add(a);
}
}
String[] r = new String[res.size()];
for (int i = 0; i < res.size(); i++) {
r[i] = res.get(i);
}
return r;
}
private static boolean destinationDirectoryValid(String dest) {
if (dest == null) {
System.out.println("no -dest parameter provided");

View File

@ -293,14 +293,10 @@ public class ValidationService {
System.out.println((error == 0 ? "Success" : "*FAILURE*") + ": " + Integer.toString(error) + " errors, " + Integer.toString(warn) + " warnings, " + Integer.toString(info) + " notes");
for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) {
System.out.println(getIssueSummary(issue));
if (crumbs) {
ValidationMessage vm = (ValidationMessage) issue.getUserData("source.msg");
if (vm != null) {
if (vm.sliceText != null) {
for (String s : vm.sliceText) {
System.out.println(" slice info: "+s);
}
}
ValidationMessage vm = (ValidationMessage) issue.getUserData("source.msg");
if (vm != null && vm.sliceText != null && (crumbs || vm.isCriticalSignpost())) {
for (String s : vm.sliceText) {
System.out.println(" slice info: "+s);
}
}
}

View File

@ -93,6 +93,7 @@ import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
@ -303,7 +304,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
} else if (item instanceof Element) {
Element e = (Element) item;
self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage));
if (e.getName().equals("contained")) {
self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e, ctxt.getRootResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage));
} else {
self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage));
}
} else
throw new NotImplementedException(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCONFORMSTOPROFILE_WHEN_ITEM_IS_NOT_AN_ELEMENT));
boolean ok = true;
@ -2753,7 +2758,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!isShowMessagesFromReferences()) {
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles), I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile()));
for (StructureDefinition sd : badProfiles.keySet()) {
slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false,
slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, false,
context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()),
errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd)));
}
@ -2771,7 +2776,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!isShowMessagesFromReferences()) {
warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet()));
for (StructureDefinition sd : badProfiles.keySet()) {
slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()),
slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, false, context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()),
errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd)));
}
} else {
@ -2898,6 +2903,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return "<ul>" + b.toString() + "</ul>";
}
private boolean isCritical(List<ValidationMessage> list) {
for (ValidationMessage vm : list) {
if (vm.isSlicingHint() && vm.isCriticalSignpost()) {
return true;
}
}
return false;
}
private String[] errorSummaryForSlicingAsText(List<ValidationMessage> list) {
List<String> res = new ArrayList<String>();
for (ValidationMessage vm : list) {
@ -3305,7 +3319,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
rr.setExternal(false);
rr.setStack(nstack);
// rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")");
System.out.println("-->"+nstack.getLiteralPath());
// System.out.println("-->"+nstack.getLiteralPath());
return rr;
}
if (nstack.getElement().getSpecial() == SpecialElement.CONTAINED) {
@ -3675,9 +3689,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ValidatorHostContext shc = hostContext.forSlicing();
boolean pass = evaluateSlicingExpression(shc, element, path, profile, n);
if (!pass) {
slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, (context.formatMessage(I18nConstants.DOES_NOT_MATCH_SLICE_, ed.getSliceName())), "discriminator = " + Utilities.escapeXml(n.toString()), null);
slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, isProfile(slicer), (context.formatMessage(I18nConstants.DOES_NOT_MATCH_SLICE_, ed.getSliceName())), "discriminator = " + Utilities.escapeXml(n.toString()), null);
for (String url : shc.getSliceRecords().keySet()) {
slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false,
slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, isProfile(slicer),
context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, stack.getLiteralPath(), url),
context.formatMessage(I18nConstants.PROFILE__DOES_NOT_MATCH_FOR__BECAUSE_OF_THE_FOLLOWING_PROFILE_ISSUES__,
url,
@ -3687,6 +3701,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return pass;
}
private boolean isProfile(ElementDefinition slicer) {
if (slicer == null || !slicer.hasSlicing()) {
return false;
}
for (ElementDefinitionSlicingDiscriminatorComponent t : slicer.getSlicing().getDiscriminator()) {
if (t.getType() == DiscriminatorType.PROFILE) {
return true;
}
}
return false;
}
public boolean evaluateSlicingExpression(ValidatorHostContext hostContext, Element element, String path, StructureDefinition profile, ExpressionNode n) throws FHIRException {
String msg;
boolean ok;
@ -4898,7 +4924,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (ei.additionalSlice && ei.definition != null) {
if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) ||
ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPENATEND) && true /* TODO: replace "true" with condition to check that this element is at "end" */) {
slicingHint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false,
slicingHint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false, isProfile(slicer) || isCritical(ei.sliceInfo),
context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_,
profile == null ? "" : " defined in the profile " + profile.getUrl()),
context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : I18nConstants.DEFINED_IN_THE_PROFILE + profile.getUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo),
@ -4944,6 +4970,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return problematicPaths;
}
public List<ElementInfo> listChildren(Element element, NodeStack stack) {
// 1. List the children, and remember their exact path (convenience)
List<ElementInfo> children = new ArrayList<ElementInfo>();
@ -5197,7 +5224,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
// todo: validate everything in this bundle.
}
ok = rule(errors, IssueType.INVALID, -1, -1, stack.getLiteralPath(), typeMatchesDefn(resourceName, defn), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE, defn.getName(), resourceName);
ok = rule(errors, IssueType.INVALID, -1, -1, stack.getLiteralPath(), typeMatchesDefn(resourceName, defn), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE, defn.getType(), resourceName, defn.getName());
if (ok) {
if (idstatus == IdStatus.REQUIRED && (element.getNamedChild(ID) == null)) {

View File

@ -192,9 +192,13 @@ public class QuestionnaireValidator extends BaseValidator {
hint(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), questionnaire != null, I18nConstants.QUESTIONNAIRE_QR_Q_NONE);
if (ok) {
Questionnaire qsrc = questionnaire.startsWith("#") ? loadQuestionnaire(element, questionnaire.substring(1)) : context.fetchResource(Questionnaire.class, questionnaire);
ok = questionnaireMode == QuestionnaireMode.REQUIRED ?
rule(errors, IssueType.REQUIRED, q.line(), q.col(), stack.getLiteralPath(), qsrc != null, I18nConstants.QUESTIONNAIRE_QR_Q_NOTFOUND, questionnaire) :
warning(errors, IssueType.REQUIRED, q.line(), q.col(), stack.getLiteralPath(), qsrc != null, I18nConstants.QUESTIONNAIRE_QR_Q_NOTFOUND, questionnaire);
if (questionnaireMode == QuestionnaireMode.REQUIRED) {
ok = rule(errors, IssueType.REQUIRED, q.line(), q.col(), stack.getLiteralPath(), qsrc != null, I18nConstants.QUESTIONNAIRE_QR_Q_NOTFOUND, questionnaire);
} else if (questionnaire.startsWith("http://example.org")) {
ok = hint(errors, IssueType.REQUIRED, q.line(), q.col(), stack.getLiteralPath(), qsrc != null, I18nConstants.QUESTIONNAIRE_QR_Q_NOTFOUND, questionnaire);
} else {
ok = warning(errors, IssueType.REQUIRED, q.line(), q.col(), stack.getLiteralPath(), qsrc != null, I18nConstants.QUESTIONNAIRE_QR_Q_NOTFOUND, questionnaire);
}
if (ok) {
boolean inProgress = "in-progress".equals(element.getNamedChildValue("status"));
validateQuestionannaireResponseItems(hostContext, qsrc, qsrc.getItem(), errors, element, stack, inProgress, element, new QStack(qsrc, element));

View File

@ -15,10 +15,12 @@ import org.hl7.fhir.convertors.factory.*;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.ProfileUtilities;
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.Manager;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.Resource;
@ -35,6 +37,7 @@ 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.utilities.validation.ValidationOptions;
import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.TimeTracker;
import org.hl7.fhir.validation.instance.utils.NodeStack;
@ -209,13 +212,23 @@ public class StructureDefinitionValidator extends BaseValidator {
String ref = valueSet.hasPrimitiveValue() ? valueSet.primitiveValue() : valueSet.getNamedChildValue("reference");
if (warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || ref != null, I18nConstants.SD_ED_SHOULD_BIND_WITH_VS, path)) {
Resource vs = context.fetchResource(Resource.class, ref);
if (warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs != null, I18nConstants.SD_ED_BIND_UNKNOWN_VS, path, ref)) {
rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType());
// just because we can't resolve it directly doesn't mean that terminology server can't. Check with it
if (warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs != null || serverSupportsValueSet(ref), I18nConstants.SD_ED_BIND_UNKNOWN_VS, path, ref)) {
if (vs != null) {
rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType());
}
}
}
}
}
private boolean serverSupportsValueSet(String ref) {
ValidationResult vr = context.validateCode(new ValidationOptions().checkValueSetOnly().setVsAsUrl().noClient(), new Coding("http://loinc.org", "5792-7", null), new ValueSet().setUrl(ref));
return vr.getErrorClass() == null;
}
private void validateElementType(List<ValidationMessage> errors, Element type, NodeStack stack, StructureDefinition sd, String path) {
String code = type.getNamedChildValue("code");
if (code == null && path != null) {

View File

@ -26,11 +26,20 @@ public class ValidatorHostContext {
}
public ValidatorHostContext(Object appContext, Element element) {
this.appContext = appContext;
this.resource = element;
this.rootResource = element;
// no container
}
this.appContext = appContext;
this.resource = element;
this.rootResource = element;
// no container
dump("creating");
}
public ValidatorHostContext(Object appContext, Element element, Element root) {
this.appContext = appContext;
this.resource = element;
this.rootResource = root;
// no container
dump("creating");
}
public Object getAppContext() {
return appContext;
@ -52,6 +61,7 @@ public class ValidatorHostContext {
public ValidatorHostContext setRootResource(Element rootResource) {
this.rootResource = rootResource;
dump("setting root resource");
return this;
}
@ -96,6 +106,7 @@ public class ValidatorHostContext {
res.rootResource = resource;
res.resource = element;
res.profile = profile;
res.dump("forContained");
return res;
}
@ -104,6 +115,7 @@ public class ValidatorHostContext {
res.rootResource = element;
res.resource = element;
res.profile = profile;
res.dump("forEntry");
return res;
}
@ -113,6 +125,7 @@ public class ValidatorHostContext {
res.rootResource = rootResource;
res.profile = profile;
res.sliceRecords = sliceRecords != null ? sliceRecords : new HashMap<String, List<ValidationMessage>>();
res.dump("forProfile "+profile.getUrl());
return res;
}
@ -122,15 +135,24 @@ public class ValidatorHostContext {
res.rootResource = resource;
res.profile = profile;
res.checkSpecials = false;
res.dump("forLocalReference "+profile.getUrl());
return res;
}
private void dump(String ctxt) {
// System.out.println("** app = "+(appContext == null ? "(null)" : appContext.toString())+", res = "+resource.toString()+", root = "+rootResource.toString()+" ("+ctxt+")");
// if (rootResource.getName().equals("contained")) {
// System.out.println("** something is wrong!");
// }
}
public ValidatorHostContext forRemoteReference(StructureDefinition profile, Element resource) {
ValidatorHostContext res = new ValidatorHostContext(appContext);
res.resource = resource;
res.rootResource = resource;
res.profile = profile;
res.checkSpecials = false;
res.dump("forRemoteReference "+profile.getUrl());
return res;
}
@ -141,6 +163,7 @@ public class ValidatorHostContext {
res.profile = profile;
res.checkSpecials = false;
res.sliceRecords = new HashMap<String, List<ValidationMessage>>();
res.dump("forSlicing");
return res;
}