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() { public T getVersionConvertor() {
T result = threadLocalVersionConverter.get(); T result = threadLocalVersionConverter.get();
logger.debug(result.toString()); if (result != null && logger != null) {
logger.debug(result.toString());
}
return result; return result;
} }
} }

View File

@ -516,7 +516,7 @@ public class FHIRToolingClient {
return p_out; return p_out;
} }
} catch (Exception e) { } 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; return null;
} }

View File

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

View File

@ -277,11 +277,15 @@ public class FHIRToolingClient {
URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps); URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
if (complex) { if (complex) {
byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat())); 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(), result = client.issuePostRequest(url, body, getPreferredResourceFormat(),
"POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG); "POST " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
} else { } 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); result = client.issueGetResourceRequest(url, getPreferredResourceFormat(), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, TIMEOUT_OPERATION_LONG);
} }
if (result.isUnsuccessfulRequest()) { if (result.isUnsuccessfulRequest()) {
@ -295,7 +299,7 @@ public class FHIRToolingClient {
return p_out; return p_out;
} }
} catch (Exception e) { } 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; return null;
} }

View File

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

View File

@ -2452,19 +2452,19 @@ public class ProfileUtilities extends TranslatingUtilities {
if (webUrl != null) { if (webUrl != null) {
// also, must touch up the markdown // also, must touch up the markdown
if (element.hasDefinition()) if (element.hasDefinition())
element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl)); element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl, baseSpecUrl()));
if (element.hasComment()) if (element.hasComment())
element.setComment(processRelativeUrls(element.getComment(), webUrl)); element.setComment(processRelativeUrls(element.getComment(), webUrl, baseSpecUrl()));
if (element.hasRequirements()) if (element.hasRequirements())
element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl)); element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl, baseSpecUrl()));
if (element.hasMeaningWhenMissing()) if (element.hasMeaningWhenMissing())
element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl)); element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl, baseSpecUrl()));
} }
} }
return element; return element;
} }
private String processRelativeUrls(String markdown, String webUrl) { public static String processRelativeUrls(String markdown, String webUrl, String basePath) {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
int i = 0; int i = 0;
while (i < markdown.length()) { while (i < markdown.length()) {
@ -2487,7 +2487,7 @@ public class ProfileUtilities extends TranslatingUtilities {
// //
if (isLikelySourceURLReference(url)) { if (isLikelySourceURLReference(url)) {
b.append("]("); b.append("](");
b.append(baseSpecUrl()); b.append(basePath);
i = i + 1; i = i + 1;
} else { } else {
b.append("]("); b.append("](");
@ -2507,11 +2507,13 @@ public class ProfileUtilities extends TranslatingUtilities {
} }
private boolean isLikelySourceURLReference(String url) { public static boolean isLikelySourceURLReference(String url) {
return return
url.startsWith("extensibility.html") || url.startsWith("extensibility.html") ||
url.startsWith("terminologies.html") ||
url.startsWith("observation.html") || url.startsWith("observation.html") ||
url.startsWith("datatypes.html") || url.startsWith("datatypes.html") ||
url.startsWith("narrative.html") ||
(url.startsWith("extension-") || url.contains(".html")) || (url.startsWith("extension-") || url.contains(".html")) ||
url.startsWith("resource-definitions.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)); pIn.addParameter().setName("implySystem").setValue(new BooleanType(true));
} }
setTerminologyOptions(options, pIn); setTerminologyOptions(options, pIn);
res = validateOnServer(vs, pIn); res = validateOnServer(vs, pIn, options);
} catch (Exception e) { } 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) { if (res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
unsupportedCodeSystems.add(code.getSystem()); unsupportedCodeSystems.add(code.getSystem());
@ -967,7 +967,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
Parameters pIn = new Parameters(); Parameters pIn = new Parameters();
pIn.addParameter().setName("codeableConcept").setValue(code); pIn.addParameter().setName("codeableConcept").setValue(code);
setTerminologyOptions(options, pIn); setTerminologyOptions(options, pIn);
res = validateOnServer(vs, pIn); res = validateOnServer(vs, pIn, options);
} catch (Exception e) { } catch (Exception e) {
res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()).setTxLink(txLog.getLastId()); 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; 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; boolean cache = false;
if (vs != null) { if (vs != null) {
for (ConceptSetComponent inc : vs.getCompose().getInclude()) { for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
@ -988,6 +988,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
if (vs != null) { if (vs != null) {
if (isTxCaching && cacheId != null && cached.contains(vs.getUrl()+"|"+vs.getVersion())) { if (isTxCaching && cacheId != null && cached.contains(vs.getUrl()+"|"+vs.getVersion())) {
pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()+(vs.hasVersion() ? "|"+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 { } else {
pin.addParameter().setName("valueSet").setResource(vs); pin.addParameter().setName("valueSet").setResource(vs);
cached.add(vs.getUrl()+"|"+vs.getVersion()); cached.add(vs.getUrl()+"|"+vs.getVersion());

View File

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

View File

@ -634,7 +634,14 @@ public class Element extends Base {
@Override @Override
public String toString() { 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 @Override

View File

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

View File

@ -422,7 +422,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
if (first) first = false; else td.addText(", "); if (first) first = false; else td.addText(", ");
if (pcv.hasValueCoding()) { if (pcv.hasValueCoding()) {
td.addText(pcv.getValueCoding().getCode()); 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()); td.ah(pcv.getValue().primitiveValue()).tx(pcv.getValue().primitiveValue());
} else { } else {
td.addText(pcv.getValue().primitiveValue()); 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 x.addText(((Enumeration<?>) e).getValue().toString()); // todo: look up a display name if there is one
return true; return true;
} else if (e instanceof BooleanType) { } else if (e instanceof BooleanType) {
if (((BooleanType) e).getValue()) { if (((BooleanType) e).hasValue()) {
x.addText(name); x.addText(name);
x.addText(": ");
x.addText(((BooleanType) e).getValueAsString());
return true; return true;
} }
} else if (e instanceof CodeableReference) { } 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 { private void defn(XhtmlNode tbl, String name, String url, Resource res) throws UnsupportedEncodingException, IOException {
if (res != null && res.hasUserData("path")) { if (res != null && res.hasUserData("path")) {
defn(tbl, "Definition", RendererFactory.factory(res, context).display(res), res.getUserString("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, url);
} { } {
defn(tbl, "Definition", url); defn(tbl, "Definition", url);

View File

@ -84,7 +84,7 @@ public class SpreadsheetGenerator {
if (name.length() > MAX_SENSITIVE_SHEET_NAME_LEN - 2) { if (name.length() > MAX_SENSITIVE_SHEET_NAME_LEN - 2) {
name = name.substring(0, 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)) { if (sheetNames.contains(s)) {
int i = 1; int i = 1;
do { do {
@ -96,6 +96,26 @@ public class SpreadsheetGenerator {
return wb.createSheet(s); 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){ private static Map<String, CellStyle> createStyles(Workbook wb){
Map<String, CellStyle> styles = new HashMap<>(); 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", "Slicing Discriminator", "Slicing Description", "Slicing Ordered", "Slicing Rules", "Base Path", "Base Min", "Base Max",
"Condition(s)", "Constraint(s)"}; "Condition(s)", "Constraint(s)"};
public StructureDefinitionSpreadsheetGenerator(IWorkerContext context, boolean asXml, boolean hideMustSupportFalse) { public StructureDefinitionSpreadsheetGenerator(IWorkerContext context, boolean valuesAsXml, boolean hideMustSupportFalse) {
super(context); super(context);
this.asXml = asXml; this.asXml = valuesAsXml;
this.hideMustSupportFalse = hideMustSupportFalse; this.hideMustSupportFalse = hideMustSupportFalse;
} }

View File

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

View File

@ -1098,7 +1098,17 @@ public class Utilities {
if (ref != null && ref.contains(":")) { if (ref != null && ref.contains(":")) {
String scheme = ref.substring(0, ref.indexOf(":")); String scheme = ref.substring(0, ref.indexOf(":"));
String details = ref.substring(ref.indexOf(":")+1); 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 && details != null && details.length() > 0 && !details.contains(" "); // rfc5141
} }
return false; return false;

View File

@ -135,7 +135,7 @@ public class VersionUtilities {
} }
public static boolean isR5Ver(String ver) { 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) { 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"; public static final String SECONDARY_SERVER = "https://packages2.fhir.org/packages";
// private static final String SECONDARY_SERVER = "http://local.fhir.org:960/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_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\\-\\_]+)*)?$"; 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 Logger ourLog = LoggerFactory.getLogger(FilesystemPackageCacheManager.class);
private static final String CACHE_VERSION = "3"; // second version - see wiki page 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"); throw new FHIRException("Cannot add package " + id + " to the package cache - the version '" + version + "' is illegal in this context");
} }
for (char ch : version.toCharArray()) { 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 + "'"); 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) // nup, don't have it locally (or it's expired)
FilesystemPackageCacheManager.InputStreamWithSrc source; FilesystemPackageCacheManager.InputStreamWithSrc source;
if ("current".equals(version)) { if ("current".equals(version) || version.startsWith("current$")) {
// special case - fetch from ci-build server // special case - fetch from ci-build server
source = loadFromCIBuild(id); source = loadFromCIBuild(id, version.startsWith("current$") ? version.substring(8) : null);
} else { } else {
source = loadFromPackageServer(id, version); source = loadFromPackageServer(id, version);
} }
@ -524,15 +524,20 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
if (optional) if (optional)
return null; return null;
else 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(); checkBuildLoaded();
if (ciList.containsKey(id)) { if (ciList.containsKey(id)) {
InputStream stream = fetchFromUrlSpecific(Utilities.pathURL(ciList.get(id), "package.tgz"), false); if (branch == null) {
return new InputStreamWithSrc(stream, Utilities.pathURL(ciList.get(id), "package.tgz"), "current"); 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")) { } else if (id.startsWith("hl7.fhir.r5")) {
InputStream stream = fetchFromUrlSpecific(Utilities.pathURL("http://build.fhir.org", id + ".tgz"), false); 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"); 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; public String[] sliceText;
private boolean slicingHint; private boolean slicingHint;
private boolean signpost; private boolean signpost;
private boolean criticalSignpost;
/** /**
@ -772,9 +773,10 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
return sliceHtml; return sliceHtml;
} }
public void setSliceHtml(String sliceHtml, String[] text) { public ValidationMessage setSliceHtml(String sliceHtml, String[] text) {
this.sliceHtml = sliceHtml; this.sliceHtml = sliceHtml;
this.sliceText = text; this.sliceText = text;
return this;
} }
public String getMessageId() { public String getMessageId() {
@ -795,5 +797,14 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
return this; 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 useClient = true;
private boolean guessSystem = false; private boolean guessSystem = false;
private ValueSetMode valueSetMode = ValueSetMode.ALL_CHECKS; private ValueSetMode valueSetMode = ValueSetMode.ALL_CHECKS;
private boolean vsAsUrl;
public ValidationOptions() { public ValidationOptions() {
super(); super();
@ -42,7 +43,8 @@ public class ValidationOptions {
ValidationOptions n = new ValidationOptions(language); ValidationOptions n = new ValidationOptions(language);
n.useServer = useServer; n.useServer = useServer;
n.useClient = useClient; n.useClient = useClient;
n.guessSystem = guessSystem; n.guessSystem = guessSystem;
n.vsAsUrl = vsAsUrl;
return n; return n;
} }
@ -96,5 +98,14 @@ public class ValidationOptions {
return valueSetMode; 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 = 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_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_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} Validation_VAL_Unknown_Profile = Unknown profile {0}
XHTML_XHTML_Attribute_Illegal = Illegal attribute name in the XHTML (''{0}'' on ''{1}'') XHTML_XHTML_Attribute_Illegal = Illegal attribute name in the XHTML (''{0}'' on ''{1}'')
XHTML_XHTML_Element_Illegal = Illegal element name in the XHTML (''{0}'') 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_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_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 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_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} 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) 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 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.ArrayList;
import java.util.Date;
import java.util.HashMap; 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) * @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 //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) { 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; return thePass;
} }

View File

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

View File

@ -71,6 +71,8 @@ import org.hl7.fhir.validation.cli.utils.*;
import java.io.File; import java.io.File;
import java.net.Authenticator; import java.net.Authenticator;
import java.net.PasswordAuthentication; import java.net.PasswordAuthentication;
import java.util.ArrayList;
import java.util.List;
/** /**
* A executable class that will validate one or more FHIR resources against * A executable class that will validate one or more FHIR resources against
@ -101,6 +103,8 @@ public class ValidatorCli {
TimeTracker tt = new TimeTracker(); TimeTracker tt = new TimeTracker();
TimeTracker.Session tts = tt.start("Loading"); TimeTracker.Session tts = tt.start("Loading");
args = preProcessArgs(args);
Display.displayVersion(); Display.displayVersion();
Display.displaySystemInfo(); 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) { private static boolean destinationDirectoryValid(String dest) {
if (dest == null) { if (dest == null) {
System.out.println("no -dest parameter provided"); 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"); 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()) { for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) {
System.out.println(getIssueSummary(issue)); System.out.println(getIssueSummary(issue));
if (crumbs) { ValidationMessage vm = (ValidationMessage) issue.getUserData("source.msg");
ValidationMessage vm = (ValidationMessage) issue.getUserData("source.msg"); if (vm != null && vm.sliceText != null && (crumbs || vm.isCriticalSignpost())) {
if (vm != null) { for (String s : vm.sliceText) {
if (vm.sliceText != null) { System.out.println(" slice info: "+s);
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.ElementDefinitionBindingComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; 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.ElementDefinitionSlicingDiscriminatorComponent;
import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation; import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
@ -303,7 +304,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} else if (item instanceof Element) { } else if (item instanceof Element) {
Element e = (Element) item; 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 } else
throw new NotImplementedException(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCONFORMSTOPROFILE_WHEN_ITEM_IS_NOT_AN_ELEMENT)); throw new NotImplementedException(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCONFORMSTOPROFILE_WHEN_ITEM_IS_NOT_AN_ELEMENT));
boolean ok = true; boolean ok = true;
@ -2753,7 +2758,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!isShowMessagesFromReferences()) { if (!isShowMessagesFromReferences()) {
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles), I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile())); rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles), I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile()));
for (StructureDefinition sd : badProfiles.keySet()) { 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()), context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()),
errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd))); errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd)));
} }
@ -2771,7 +2776,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!isShowMessagesFromReferences()) { if (!isShowMessagesFromReferences()) {
warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet())); warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet()));
for (StructureDefinition sd : badProfiles.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))); errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd)));
} }
} else { } else {
@ -2898,6 +2903,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return "<ul>" + b.toString() + "</ul>"; 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) { private String[] errorSummaryForSlicingAsText(List<ValidationMessage> list) {
List<String> res = new ArrayList<String>(); List<String> res = new ArrayList<String>();
for (ValidationMessage vm : list) { for (ValidationMessage vm : list) {
@ -3305,7 +3319,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
rr.setExternal(false); rr.setExternal(false);
rr.setStack(nstack); rr.setStack(nstack);
// rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")"); // rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")");
System.out.println("-->"+nstack.getLiteralPath()); // System.out.println("-->"+nstack.getLiteralPath());
return rr; return rr;
} }
if (nstack.getElement().getSpecial() == SpecialElement.CONTAINED) { if (nstack.getElement().getSpecial() == SpecialElement.CONTAINED) {
@ -3675,9 +3689,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ValidatorHostContext shc = hostContext.forSlicing(); ValidatorHostContext shc = hostContext.forSlicing();
boolean pass = evaluateSlicingExpression(shc, element, path, profile, n); boolean pass = evaluateSlicingExpression(shc, element, path, profile, n);
if (!pass) { 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()) { 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.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, stack.getLiteralPath(), url),
context.formatMessage(I18nConstants.PROFILE__DOES_NOT_MATCH_FOR__BECAUSE_OF_THE_FOLLOWING_PROFILE_ISSUES__, context.formatMessage(I18nConstants.PROFILE__DOES_NOT_MATCH_FOR__BECAUSE_OF_THE_FOLLOWING_PROFILE_ISSUES__,
url, url,
@ -3687,6 +3701,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return pass; 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 { public boolean evaluateSlicingExpression(ValidatorHostContext hostContext, Element element, String path, StructureDefinition profile, ExpressionNode n) throws FHIRException {
String msg; String msg;
boolean ok; boolean ok;
@ -4898,7 +4924,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (ei.additionalSlice && ei.definition != null) { if (ei.additionalSlice && ei.definition != null) {
if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) || 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" */) { 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_, context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_,
profile == null ? "" : " defined in the profile " + profile.getUrl()), 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), 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; return problematicPaths;
} }
public List<ElementInfo> listChildren(Element element, NodeStack stack) { public List<ElementInfo> listChildren(Element element, NodeStack stack) {
// 1. List the children, and remember their exact path (convenience) // 1. List the children, and remember their exact path (convenience)
List<ElementInfo> children = new ArrayList<ElementInfo>(); List<ElementInfo> children = new ArrayList<ElementInfo>();
@ -5197,7 +5224,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
// todo: validate everything in this bundle. // 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 (ok) {
if (idstatus == IdStatus.REQUIRED && (element.getNamedChild(ID) == null)) { 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); hint(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), questionnaire != null, I18nConstants.QUESTIONNAIRE_QR_Q_NONE);
if (ok) { if (ok) {
Questionnaire qsrc = questionnaire.startsWith("#") ? loadQuestionnaire(element, questionnaire.substring(1)) : context.fetchResource(Questionnaire.class, questionnaire); Questionnaire qsrc = questionnaire.startsWith("#") ? loadQuestionnaire(element, questionnaire.substring(1)) : context.fetchResource(Questionnaire.class, questionnaire);
ok = questionnaireMode == QuestionnaireMode.REQUIRED ? if (questionnaireMode == QuestionnaireMode.REQUIRED) {
rule(errors, IssueType.REQUIRED, q.line(), q.col(), stack.getLiteralPath(), qsrc != null, I18nConstants.QUESTIONNAIRE_QR_Q_NOTFOUND, questionnaire) : ok = 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); } 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) { if (ok) {
boolean inProgress = "in-progress".equals(element.getNamedChildValue("status")); boolean inProgress = "in-progress".equals(element.getNamedChildValue("status"));
validateQuestionannaireResponseItems(hostContext, qsrc, qsrc.getItem(), errors, element, stack, inProgress, element, new QStack(qsrc, element)); 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.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.ProfileUtilities; import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext; 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.Element;
import org.hl7.fhir.r5.elementmodel.Manager; import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.formats.IParser.OutputStyle; 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.ElementDefinition;
import org.hl7.fhir.r5.model.ExpressionNode; import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.Resource; 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;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 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.BaseValidator;
import org.hl7.fhir.validation.TimeTracker; import org.hl7.fhir.validation.TimeTracker;
import org.hl7.fhir.validation.instance.utils.NodeStack; 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"); 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)) { 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); 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) { private void validateElementType(List<ValidationMessage> errors, Element type, NodeStack stack, StructureDefinition sd, String path) {
String code = type.getNamedChildValue("code"); String code = type.getNamedChildValue("code");
if (code == null && path != null) { if (code == null && path != null) {

View File

@ -26,11 +26,20 @@ public class ValidatorHostContext {
} }
public ValidatorHostContext(Object appContext, Element element) { public ValidatorHostContext(Object appContext, Element element) {
this.appContext = appContext; this.appContext = appContext;
this.resource = element; this.resource = element;
this.rootResource = element; this.rootResource = element;
// no container // 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() { public Object getAppContext() {
return appContext; return appContext;
@ -52,6 +61,7 @@ public class ValidatorHostContext {
public ValidatorHostContext setRootResource(Element rootResource) { public ValidatorHostContext setRootResource(Element rootResource) {
this.rootResource = rootResource; this.rootResource = rootResource;
dump("setting root resource");
return this; return this;
} }
@ -96,6 +106,7 @@ public class ValidatorHostContext {
res.rootResource = resource; res.rootResource = resource;
res.resource = element; res.resource = element;
res.profile = profile; res.profile = profile;
res.dump("forContained");
return res; return res;
} }
@ -104,6 +115,7 @@ public class ValidatorHostContext {
res.rootResource = element; res.rootResource = element;
res.resource = element; res.resource = element;
res.profile = profile; res.profile = profile;
res.dump("forEntry");
return res; return res;
} }
@ -113,6 +125,7 @@ public class ValidatorHostContext {
res.rootResource = rootResource; res.rootResource = rootResource;
res.profile = profile; res.profile = profile;
res.sliceRecords = sliceRecords != null ? sliceRecords : new HashMap<String, List<ValidationMessage>>(); res.sliceRecords = sliceRecords != null ? sliceRecords : new HashMap<String, List<ValidationMessage>>();
res.dump("forProfile "+profile.getUrl());
return res; return res;
} }
@ -122,15 +135,24 @@ public class ValidatorHostContext {
res.rootResource = resource; res.rootResource = resource;
res.profile = profile; res.profile = profile;
res.checkSpecials = false; res.checkSpecials = false;
res.dump("forLocalReference "+profile.getUrl());
return res; 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) { public ValidatorHostContext forRemoteReference(StructureDefinition profile, Element resource) {
ValidatorHostContext res = new ValidatorHostContext(appContext); ValidatorHostContext res = new ValidatorHostContext(appContext);
res.resource = resource; res.resource = resource;
res.rootResource = resource; res.rootResource = resource;
res.profile = profile; res.profile = profile;
res.checkSpecials = false; res.checkSpecials = false;
res.dump("forRemoteReference "+profile.getUrl());
return res; return res;
} }
@ -141,6 +163,7 @@ public class ValidatorHostContext {
res.profile = profile; res.profile = profile;
res.checkSpecials = false; res.checkSpecials = false;
res.sliceRecords = new HashMap<String, List<ValidationMessage>>(); res.sliceRecords = new HashMap<String, List<ValidationMessage>>();
res.dump("forSlicing");
return res; return res;
} }