Merge branch 'hapifhir:master' into master

This commit is contained in:
Vassil Peytchev 2023-11-01 14:44:46 -05:00 committed by GitHub
commit 4c4156d539
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 500 additions and 213 deletions

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>6.2.1-SNAPSHOT</version> <version>6.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>6.2.1-SNAPSHOT</version> <version>6.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>6.2.1-SNAPSHOT</version> <version>6.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>6.2.1-SNAPSHOT</version> <version>6.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>6.2.1-SNAPSHOT</version> <version>6.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -469,10 +469,12 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
tlog("$expand on " + txCache.summary(vs)); tlog("$expand on " + txCache.summary(vs));
try { try {
ValueSet result = txClient.expandValueset(vs, p, params); ValueSet result = txClient.expandValueset(vs, p, params);
if (result != null) {
if (!result.hasUrl()) if (!result.hasUrl())
result.setUrl(vs.getUrl()); result.setUrl(vs.getUrl());
if (!result.hasUrl()) if (!result.hasUrl())
throw new Error("no url in expand value set 2"); throw new Error("no url in expand value set 2");
}
res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId()); res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());
} catch (Exception e) { } catch (Exception e) {
res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(),

View File

@ -276,9 +276,6 @@ public class FhirRequestBuilder {
if (error != null) { if (error != null) {
String s = ResourceUtilities.getErrorDescription(error); String s = ResourceUtilities.getErrorDescription(error);
System.out.println(s); System.out.println(s);
if (s.startsWith("Unable to find value set")) {
System.out.println("!");
}
throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error); throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error);
} }

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>6.2.1-SNAPSHOT</version> <version>6.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -805,12 +805,14 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
tlog("$expand on " + txCache.summary(vs)); tlog("$expand on " + txCache.summary(vs));
try { try {
ValueSet result = txClient.expandValueset(vs, p, params); ValueSet result = txClient.expandValueset(vs, p, params);
if (result != null) {
if (!result.hasUrl()) { if (!result.hasUrl()) {
result.setUrl(vs.getUrl()); result.setUrl(vs.getUrl());
} }
if (!result.hasUrl()) { if (!result.hasUrl()) {
throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET_2)); throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET_2));
} }
}
res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId()); res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());
} catch (Exception e) { } catch (Exception e) {
res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(),

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>6.2.1-SNAPSHOT</version> <version>6.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -952,12 +952,14 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
txLog("$expand on "+txCache.summary(vs)); txLog("$expand on "+txCache.summary(vs));
try { try {
ValueSet result = tcc.getClient().expandValueset(vs, p, params); ValueSet result = tcc.getClient().expandValueset(vs, p, params);
if (result != null) {
if (!result.hasUrl()) { if (!result.hasUrl()) {
result.setUrl(vs.getUrl()); result.setUrl(vs.getUrl());
} }
if (!result.hasUrl()) { if (!result.hasUrl()) {
throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET_2)); throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET_2));
} }
}
res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId()); res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());
} catch (Exception e) { } catch (Exception e) {
if (res != null && !res.isFromServer()) { if (res != null && !res.isFromServer()) {
@ -1375,7 +1377,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
if (valueSet != null) { if (valueSet != null) {
pIn.addParameter().setName("valueSet").setResource(valueSet); pIn.addParameter().setName("valueSet").setResource(valueSet);
} }
pIn.addParameter().setName("profile").setResource(expParameters);
pIn.addParameters(expParameters);
return pIn; return pIn;
} }
@ -1388,7 +1391,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
if (vsUrl != null) { if (vsUrl != null) {
pIn.addParameter().setName("url").setValue(new CanonicalType(vsUrl)); pIn.addParameter().setName("url").setValue(new CanonicalType(vsUrl));
} }
pIn.addParameter().setName("profile").setResource(expParameters); pIn.addParameters(expParameters);
return pIn; return pIn;
} }
@ -1548,7 +1551,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
if (expParameters == null) { if (expParameters == null) {
throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED)); throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
} }
pin.addParameter().setName("profile").setResource(expParameters); pin.addParameters(expParameters);
if (options.isDisplayWarningMode()) { if (options.isDisplayWarningMode()) {
pin.addParameter("mode","lenient-display-validation"); pin.addParameter("mode","lenient-display-validation");

View File

@ -130,7 +130,6 @@ public abstract class ParserBase {
public abstract void compose(Element e, OutputStream destination, OutputStyle style, String base) throws FHIRException, IOException; public abstract void compose(Element e, OutputStream destination, OutputStyle style, String base) throws FHIRException, IOException;
//FIXME: i18n should be done here
public void logError(List<ValidationMessage> errors, String ruleDate, int line, int col, String path, IssueType type, String message, IssueSeverity level) throws FHIRFormatError { public void logError(List<ValidationMessage> errors, String ruleDate, int line, int col, String path, IssueType type, String message, IssueSeverity level) throws FHIRFormatError {
if (errors != null) { if (errors != null) {
if (policy == ValidationPolicy.EVERYTHING) { if (policy == ValidationPolicy.EVERYTHING) {

View File

@ -730,4 +730,8 @@ public class ExpressionNode {
return names; return names;
} }
public boolean isNullSet() {
return kind == Kind.Constant && constant == null;
}
} }

View File

@ -1856,6 +1856,18 @@ public String toString() {
getParameter().removeIf(p -> name.equals(p.getName())); getParameter().removeIf(p -> name.equals(p.getName()));
} }
public void addParameters(Parameters expParameters) {
addParameters(expParameters.getParameter());
}
private void addParameters(List<ParametersParameterComponent> parameters) {
for (ParametersParameterComponent p : parameters) {
if (!hasParameter(p.getName())) {
addParameter(p);
}
}
}
// end addition // end addition
} }

View File

@ -542,7 +542,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
} else if (pcv.hasValueStringType() && Utilities.isAbsoluteUrlLinkable(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 if ("parent".equals(pcv.getCode())) { } else if ("parent".equals(pcv.getCode())) {
td.ah("#"+cs.getId()+"-"+pcv.getValue().primitiveValue()).addText(pcv.getValue().primitiveValue()); td.ah("#"+cs.getId()+"-"+Utilities.nmtokenize(pcv.getValue().primitiveValue())).addText(pcv.getValue().primitiveValue());
} else { } else {
td.addText(pcv.getValue().primitiveValue()); td.addText(pcv.getValue().primitiveValue());
} }

View File

@ -161,9 +161,17 @@ public class DataRenderer extends Renderer implements CodeResolver {
if (p == null) if (p == null)
p = getContext().getWorker().fetchResource(StructureDefinition.class, link); p = getContext().getWorker().fetchResource(StructureDefinition.class, link);
if (p != null) { if (p != null) {
if ("Extension".equals(p.getType())) {
path = null;
} else if (p.hasSnapshot()) {
path = p.getSnapshot().getElementFirstRep().getPath();
} else if (Utilities.isAbsoluteUrl(path)) {
path = null;
}
url = p.getWebPath(); url = p.getWebPath();
if (url == null) if (url == null) {
url = p.getUserString("filename"); url = p.getUserString("filename");
}
} else } else
throw new DefinitionException("Unable to resolve markdown link "+link); throw new DefinitionException("Unable to resolve markdown link "+link);

View File

@ -100,15 +100,22 @@ public class SearchParameterRenderer extends TerminologyRenderer {
} }
tr = tbl.tr(); tr = tbl.tr();
tr.td().tx("Multiples"); tr.td().tx("Multiples");
if (spd.getMultipleAnd() && spd.getMultipleOr()) { XhtmlNode ul = tr.td().ul();
tr.td().tx("The parameter can repeat (and) and can have repeating values (or)"); if (!spd.hasMultipleAnd()) {
} else if (spd.getMultipleOr()) { ul.li().tx("multipleAnd: It's up to the server whether the parameter may repeat in order to specify multiple values that must all be true");
tr.td().tx("The parameter can repeat (and) but each repeat can only have one value");
} else if (spd.getMultipleAnd()) { } else if (spd.getMultipleAnd()) {
tr.td().tx("The parameter cannot repeat (and) but the single parameter can have multiple values (or)"); ul.li().tx("multipleAnd: The parameter may repeat in order to specify multiple values that must all be true");
} else { } else {
tr.td().tx("The parameter cannot repeat or have multiple values"); ul.li().tx("multipleAnd: The parameter may only appear once");
} }
if (!spd.hasMultipleOr()) {
ul.li().tx("multipleOr: It's up to the server whether the parameter can have multiple values (separated by comma) where at least one must be true");
} else if (spd.getMultipleOr()) {
ul.li().tx("multipleOr: The parameter may have multiple values (separated by comma) where at least one must be true");
} else {
ul.li().tx("multipleOr: The parameter may only have one value (no comma separators)");
}
if (spd.hasComparator()) { if (spd.hasComparator()) {
tr = tbl.tr(); tr = tbl.tr();
tr.td().tx("Comparators"); tr.td().tx("Comparators");

View File

@ -190,6 +190,7 @@ public class RenderingContext {
private String changeVersion; private String changeVersion;
private Map<KnownLinkType, String> links = new HashMap<>(); private Map<KnownLinkType, String> links = new HashMap<>();
private Map<String, String> namedLinks = new HashMap<>();
/** /**
* *
* @param context - access to all related resources that might be needed * @param context - access to all related resources that might be needed
@ -720,5 +721,8 @@ public class RenderingContext {
return this; return this;
} }
public Map<String, String> getNamedLinks() {
return namedLinks;
}
} }

View File

@ -78,6 +78,7 @@ import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.util.ElementUtil; import ca.uhn.fhir.util.ElementUtil;
@ -511,6 +512,7 @@ public class FHIRPathEngine {
} }
types.addType(sd.getUrl()); types.addType(sd.getUrl());
} else { } else {
boolean checkTypeName = false;
String ctxt = null; String ctxt = null;
if (t.contains("#")) { if (t.contains("#")) {
ctxt = t.substring(0, t.indexOf('#')); ctxt = t.substring(0, t.indexOf('#'));
@ -518,6 +520,7 @@ public class FHIRPathEngine {
} else if (Utilities.isAbsoluteUrl(t)) { } else if (Utilities.isAbsoluteUrl(t)) {
ctxt = t; ctxt = t;
t = ctxt.substring(ctxt.lastIndexOf("/")+1); t = ctxt.substring(ctxt.lastIndexOf("/")+1);
checkTypeName = true;
} else { } else {
ctxt = t.substring(0, t.indexOf('.')); ctxt = t.substring(0, t.indexOf('.'));
} }
@ -525,7 +528,9 @@ public class FHIRPathEngine {
if (sd == null) { if (sd == null) {
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, t); throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, t);
} }
ElementDefinitionMatch ed = getElementDefinition(sd, t, true, expr); String tn = checkTypeName ? sd.getSnapshot().getElementFirstRep().getPath() : t;
ElementDefinitionMatch ed = getElementDefinition(sd, tn, true, expr);
if (ed == null) { if (ed == null) {
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, t); throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, t);
} }
@ -3087,7 +3092,12 @@ public class FHIRPathEngine {
} else if (hostServices == null) { } else if (hostServices == null) {
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s); throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
} else { } else {
return hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant); TypeDetails v = hostServices.resolveConstantType(this, context.appInfo, s, explicitConstant);
if (v == null) {
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONSTANT, s);
} else {
return v;
}
} }
} }
@ -3141,7 +3151,7 @@ public class FHIRPathEngine {
} }
private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry, TypeDetails focus, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException { private TypeDetails executeType(String type, ExpressionNode exp, boolean atEntry, TypeDetails focus, Set<ElementDefinition> elementDependencies) throws PathEngineException, DefinitionException {
if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && hashTail(type).equals(exp.getName())) { // special case for start up if (atEntry && Character.isUpperCase(exp.getName().charAt(0)) && (hashTail(type).equals(exp.getName()) || isAncestor(type, exp.getName()) )) { // special case for start up
return new TypeDetails(CollectionStatus.SINGLETON, type); return new TypeDetails(CollectionStatus.SINGLETON, type);
} }
TypeDetails result = new TypeDetails(focus.getCollectionStatus()); TypeDetails result = new TypeDetails(focus.getCollectionStatus());
@ -3150,6 +3160,21 @@ public class FHIRPathEngine {
} }
private boolean isAncestor(String wanted, String stated) {
try {
StructureDefinition sd = worker.fetchTypeDefinition(wanted);
while (sd != null) {
if (stated.equals(sd.getTypeName())) {
return true;
}
sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
}
return false;
} catch (Exception e) {
return false;
}
}
private String hashTail(String type) { private String hashTail(String type) {
return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1); return type.contains("#") ? "" : type.substring(type.lastIndexOf("/")+1);
} }
@ -3646,7 +3671,7 @@ public class FHIRPathEngine {
private void checkContextString(TypeDetails focus, String name, ExpressionNode expr, boolean sing) throws PathEngineException { private void checkContextString(TypeDetails focus, String name, ExpressionNode expr, boolean sing) throws PathEngineException {
if (!focus.hasNoTypes() && !focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) { if (!focus.hasNoTypes() && !focus.hasType(worker, "string") && !focus.hasType(worker, "code") && !focus.hasType(worker, "uri") && !focus.hasType(worker, "url") && !focus.hasType(worker, "canonical") && !focus.hasType(worker, "id")) {
throw makeException(expr, sing ? I18nConstants.FHIRPATH_STRING_SING_ONLY : I18nConstants.FHIRPATH_STRING_ORD_ONLY, name, focus.describe()); throw makeException(expr, sing ? I18nConstants.FHIRPATH_STRING_SING_ONLY : I18nConstants.FHIRPATH_STRING_ORD_ONLY, name, focus.describe());
} }
} }
@ -4314,7 +4339,11 @@ public class FHIRPathEngine {
if (x == null) { if (x == null) {
return makeBoolean(false); return makeBoolean(false);
} }
return makeBoolean(checkHtmlNames(x)); boolean ok = checkHtmlNames(x, true);
if (ok && VersionUtilities.isR6Plus(this.worker.getVersion())) {
ok = checkForContent(x);
}
return makeBoolean(ok);
} }
private List<Base> funcHtmlChecks2(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException { private List<Base> funcHtmlChecks2(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
@ -4383,12 +4412,13 @@ public class FHIRPathEngine {
return null; return null;
} }
private boolean checkHtmlNames(XhtmlNode node) { private boolean checkHtmlNames(XhtmlNode node, boolean block) {
if (node.getNodeType() == NodeType.Comment) { if (node.getNodeType() == NodeType.Comment) {
if (node.getContent().startsWith("DOCTYPE")) if (node.getContent().startsWith("DOCTYPE"))
return false; return false;
} }
if (node.getNodeType() == NodeType.Element) { if (node.getNodeType() == NodeType.Element) {
if (block) {
if (!Utilities.existsInList(node.getName(), if (!Utilities.existsInList(node.getName(),
"p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong", "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong",
"small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup",
@ -4396,6 +4426,12 @@ public class FHIRPathEngine {
"code", "samp", "img", "map", "area")) { "code", "samp", "img", "map", "area")) {
return false; return false;
} }
} else {
if (!Utilities.existsInList(node.getName(),
"a", "span", "b", "em", "i", "strong", "small", "big", "small", "q", "var", "abbr", "acronym", "cite", "kbd", "q", "sub", "sup", "code", "samp", "img", "map", "area")) {
return false;
}
}
for (String an : node.getAttributes().keySet()) { for (String an : node.getAttributes().keySet()) {
boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an,
"title", "style", "class", "id", "lang", "xml:lang", "dir", "accesskey", "tabindex", "title", "style", "class", "id", "lang", "xml:lang", "dir", "accesskey", "tabindex",
@ -4413,7 +4449,7 @@ public class FHIRPathEngine {
} }
} }
for (XhtmlNode c : node.getChildNodes()) { for (XhtmlNode c : node.getChildNodes()) {
if (!checkHtmlNames(c)) { if (!checkHtmlNames(c, block && !"p".equals(c))) {
return false; return false;
} }
} }
@ -6087,6 +6123,7 @@ public class FHIRPathEngine {
public ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName, ExpressionNode expr) throws PathEngineException { public ElementDefinitionMatch getElementDefinition(StructureDefinition sd, String path, boolean allowTypedName, ExpressionNode expr) throws PathEngineException {
for (ElementDefinition ed : sd.getSnapshot().getElement()) { for (ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.getPath().equals(path)) { if (ed.getPath().equals(path)) {
if (ed.hasContentReference()) { if (ed.hasContentReference()) {

View File

@ -13,6 +13,12 @@ public class Cell {
this.column = column; this.column = column;
} }
public Cell(Column column, Value value) {
super();
this.column = column;
this.values.add(value);
}
public Column getColumn() { public Column getColumn() {
return column; return column;
} }

View File

@ -7,6 +7,7 @@ public class Column {
private String type; private String type;
private ColumnKind kind; private ColumnKind kind;
private boolean isColl; private boolean isColl;
private boolean duplicateReported;
protected Column() { protected Column() {
super(); super();
@ -58,5 +59,41 @@ public class Column {
this.isColl = isColl; this.isColl = isColl;
} }
public String diff(Column other) {
if (!name.equals(other.name)) {
return "Names differ: '"+name+"' vs '"+other.name+"'";
}
if (kind != ColumnKind.Null && other.kind != ColumnKind.Null) {
if (length != other.length) {
return "Lengths differ: '"+length+"' vs '"+other.length+"'";
}
if (kind != other.kind) {
return "Kinds differ: '"+kind+"' vs '"+other.kind+"'";
}
if (isColl != other.isColl) {
return "Collection status differs: '"+isColl+"' vs '"+other.isColl+"'";
}
} else if (kind == ColumnKind.Null) {
kind = other.kind;
length = other.length;
isColl = other.isColl;
}
return null;
}
public boolean isDuplicateReported() {
return duplicateReported;
}
public void setDuplicateReported(boolean duplicateReported) {
this.duplicateReported = duplicateReported;
}
@Override
public String toString() {
return "Column [name=" + name + ", length=" + length + ", type=" + type + ", kind=" + kind + ", isColl=" + isColl
+ "]";
}
} }

View File

@ -1,5 +1,5 @@
package org.hl7.fhir.r5.utils.sql; package org.hl7.fhir.r5.utils.sql;
public enum ColumnKind { public enum ColumnKind {
String, DateTime, Integer, Decimal, Binary, Time, Boolean, Complex String, DateTime, Integer, Decimal, Binary, Time, Boolean, Complex, Null
} }

View File

@ -7,5 +7,5 @@ import org.hl7.fhir.r5.model.Base;
public interface Provider { public interface Provider {
List<Base> fetch(String resourceType); List<Base> fetch(String resourceType);
Base resolveReference(String ref, String resourceType); Base resolveReference(Base rootResource, String ref, String specifiedResourceType);
} }

View File

@ -24,6 +24,7 @@ import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FunctionDetails; import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FunctionDetails;
import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.validation.ValidationMessage;
public class Runner implements IEvaluationContext { public class Runner implements IEvaluationContext {
@ -33,9 +34,8 @@ public class Runner implements IEvaluationContext {
private List<String> prohibitedNames = new ArrayList<String>(); private List<String> prohibitedNames = new ArrayList<String>();
private FHIRPathEngine fpe; private FHIRPathEngine fpe;
private List<Column> columns = new ArrayList<>();
private String resourceName; private String resourceName;
private List<ValidationMessage> issues;
public IWorkerContext getContext() { public IWorkerContext getContext() {
@ -86,15 +86,15 @@ public class Runner implements IEvaluationContext {
} }
Validator validator = new Validator(context, fpe, prohibitedNames, storage.supportsArrays(), storage.supportsComplexTypes(), storage.needsName()); Validator validator = new Validator(context, fpe, prohibitedNames, storage.supportsArrays(), storage.supportsComplexTypes(), storage.needsName());
validator.checkViewDefinition(path, viewDefinition); validator.checkViewDefinition(path, viewDefinition);
issues = validator.getIssues();
validator.dump(); validator.dump();
validator.check(); validator.check();
resourceName = validator.getResourceName(); resourceName = validator.getResourceName();
columns = validator.getColumns();
evaluate(viewDefinition); evaluate(viewDefinition);
} }
private void evaluate(JsonObject vd) { private void evaluate(JsonObject vd) {
Store store = storage.createStore(vd.asString("name"), columns); Store store = storage.createStore(vd.asString("name"), (List<Column>) vd.getUserData("columns"));
List<Base> data = provider.fetch(resourceName); List<Base> data = provider.fetch(resourceName);
@ -130,7 +130,20 @@ public class Runner implements IEvaluationContext {
if (select.has("forEach")) { if (select.has("forEach")) {
focus.addAll(executeForEach(select, b)); focus.addAll(executeForEach(select, b));
} else if (select.has("forEachOrNull")) { } else if (select.has("forEachOrNull")) {
focus.addAll(executeForEachOrNull(select, b)); focus.addAll(executeForEachOrNull(select, b));
if (focus.isEmpty()) {
List<Column> columns = (List<Column>) select.getUserData("columns");
for (List<Cell> row : rows) {
for (Column c : columns) {
Cell cell = cell(row, c.getName());
if (cell == null) {
row.add(new Cell(c, null));
}
}
}
return;
}
} else { } else {
focus.add(b); focus.add(b);
} }
@ -149,19 +162,30 @@ public class Runner implements IEvaluationContext {
executeColumn(column, f, rowsToAdd); executeColumn(column, f, rowsToAdd);
} }
for (JsonObject sub : select.getJsonObjects("unionAll")) {
executeSelect(sub, f, rowsToAdd);
}
for (JsonObject sub : select.getJsonObjects("select")) { for (JsonObject sub : select.getJsonObjects("select")) {
executeSelect(sub, f, rowsToAdd); executeSelect(sub, f, rowsToAdd);
} }
executeUnionAll(select.getJsonObjects("unionAll"), f, rowsToAdd);
rows.addAll(rowsToAdd); rows.addAll(rowsToAdd);
} }
} }
private List<Base> executeUnion(JsonObject focus, Base b, List<List<Cell>> rows) { private void executeUnionAll(List<JsonObject> unionList, Base b, List<List<Cell>> rows) {
throw new FHIRException("union is not supported"); if (unionList.isEmpty()) {
return;
}
List<List<Cell>> sourceRows = new ArrayList<>();
sourceRows.addAll(rows);
rows.clear();
for (JsonObject union : unionList) {
List<List<Cell>> tempRows = new ArrayList<>();
tempRows.addAll(sourceRows);
executeSelect(union, b, tempRows);
rows.addAll(tempRows);
}
} }
private List<List<Cell>> cloneRows(List<List<Cell>> rows) { private List<List<Cell>> cloneRows(List<List<Cell>> rows) {
@ -191,9 +215,6 @@ public class Runner implements IEvaluationContext {
ExpressionNode n = (ExpressionNode) focus.getUserData("forEachOrNull"); ExpressionNode n = (ExpressionNode) focus.getUserData("forEachOrNull");
List<Base> result = new ArrayList<>(); List<Base> result = new ArrayList<>();
result.addAll(fpe.evaluate(b, n)); result.addAll(fpe.evaluate(b, n));
if (result.size() == 0) {
result.add(null);
}
return result; return result;
} }
@ -203,11 +224,14 @@ public class Runner implements IEvaluationContext {
if (b != null) { if (b != null) {
bl2.addAll(fpe.evaluate(b, n)); bl2.addAll(fpe.evaluate(b, n));
} }
String name = column.getUserString("name"); Column col = (Column) column.getUserData("column");
if (col == null) {
System.out.println("Error");
} else {
for (List<Cell> row : rows) { for (List<Cell> row : rows) {
Cell c = cell(row, name); Cell c = cell(row, col.getName());
if (c == null) { if (c == null) {
c = new Cell(column(name)); c = new Cell(col);
row.add(c); row.add(c);
} }
if (!bl2.isEmpty()) { if (!bl2.isEmpty()) {
@ -224,6 +248,7 @@ public class Runner implements IEvaluationContext {
} }
} }
} }
}
private Value genValue(Column column, Base b) { private Value genValue(Column column, Base b) {
@ -299,7 +324,7 @@ public class Runner implements IEvaluationContext {
} }
} }
private Column column(String columnName) { private Column column(String columnName, List<Column> columns) {
for (Column t : columns) { for (Column t : columns) {
if (t.getName().equalsIgnoreCase(columnName)) { if (t.getName().equalsIgnoreCase(columnName)) {
return t; return t;
@ -353,7 +378,7 @@ public class Runner implements IEvaluationContext {
public List<Base> executeFunction(FHIRPathEngine engine, Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) { public List<Base> executeFunction(FHIRPathEngine engine, Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
switch (functionName) { switch (functionName) {
case "getResourceKey" : return executeResourceKey(focus); case "getResourceKey" : return executeResourceKey(focus);
case "getReferenceKey" : return executeReferenceKey(focus, parameters); case "getReferenceKey" : return executeReferenceKey(null, focus, parameters);
default: throw new Error("Not known: "+functionName); default: throw new Error("Not known: "+functionName);
} }
} }
@ -375,7 +400,7 @@ public class Runner implements IEvaluationContext {
return base; return base;
} }
private List<Base> executeReferenceKey(List<Base> focus, List<List<Base>> parameters) { private List<Base> executeReferenceKey(Base rootResource, List<Base> focus, List<List<Base>> parameters) {
String rt = null; String rt = null;
if (parameters.size() > 0) { if (parameters.size() > 0) {
rt = parameters.get(0).get(0).primitiveValue(); rt = parameters.get(0).get(0).primitiveValue();
@ -395,7 +420,7 @@ public class Runner implements IEvaluationContext {
throw new FHIRException("Unable to generate a reference key based on a "+res.fhirType()); throw new FHIRException("Unable to generate a reference key based on a "+res.fhirType());
} }
if (ref != null) { if (ref != null) {
Base target = provider.resolveReference(ref, rt); Base target = provider.resolveReference(rootResource, ref, rt);
if (target != null) { if (target != null) {
if (!res.hasUserData("Storage.key")) { if (!res.hasUserData("Storage.key")) {
String key = storage.getKeyForTargetResource(target); String key = storage.getKeyForTargetResource(target);
@ -437,6 +462,9 @@ public class Runner implements IEvaluationContext {
public boolean paramIsType(String name, int index) { public boolean paramIsType(String name, int index) {
return "getReferenceKey".equals(name); return "getReferenceKey".equals(name);
} }
public List<ValidationMessage> getIssues() {
return issues;
}
} }

View File

@ -48,7 +48,9 @@ public class StorageJson implements Storage {
} }
private JsonElement makeJsonNode(Value value) { private JsonElement makeJsonNode(Value value) {
if (value.getValueInt() != null) { if (value == null) {
return new JsonNull();
} else if (value.getValueInt() != null) {
return new JsonNumber(value.getValueInt().intValue()); return new JsonNumber(value.getValueInt().intValue());
} }
if (value.getValueBoolean() != null) { if (value.getValueBoolean() != null) {

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.r5.utils.sql;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLType;
import java.util.List; import java.util.List;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
@ -27,7 +28,7 @@ public class StorageSqlite3 implements Storage {
private Connection conn; private Connection conn;
private int nextKey = 0; private int nextKey = 0;
protected StorageSqlite3(Connection conn) { public StorageSqlite3(Connection conn) {
super(); super();
this.conn = conn; this.conn = conn;
} }
@ -38,7 +39,7 @@ public class StorageSqlite3 implements Storage {
CommaSeparatedStringBuilder fields = new CommaSeparatedStringBuilder(", "); CommaSeparatedStringBuilder fields = new CommaSeparatedStringBuilder(", ");
CommaSeparatedStringBuilder values = new CommaSeparatedStringBuilder(", "); CommaSeparatedStringBuilder values = new CommaSeparatedStringBuilder(", ");
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append("Create Table "+name+" { "); b.append("Create Table "+name+" ( ");
b.append("ViewRowKey integer NOT NULL"); b.append("ViewRowKey integer NOT NULL");
for (Column column : columns) { for (Column column : columns) {
b.append(", "+column.getName()+" "+sqliteType(column.getKind())+" NULL"); // index columns are always nullable b.append(", "+column.getName()+" "+sqliteType(column.getKind())+" NULL"); // index columns are always nullable
@ -48,7 +49,7 @@ public class StorageSqlite3 implements Storage {
b.append(", PRIMARY KEY (ViewRowKey))\r\n"); b.append(", PRIMARY KEY (ViewRowKey))\r\n");
conn.createStatement().execute(b.toString()); conn.createStatement().execute(b.toString());
String isql = "Insert into "+name+" ("+fields.toString()+") values ("+values.toString()+")"; String isql = "Insert into "+name+" (ViewRowKey, "+fields.toString()+") values (?, "+values.toString()+")";
PreparedStatement psql = conn.prepareStatement(isql); PreparedStatement psql = conn.prepareStatement(isql);
return new SQLiteStore(name, psql); return new SQLiteStore(name, psql);
} catch (Exception e) { } catch (Exception e) {
@ -79,6 +80,8 @@ public class StorageSqlite3 implements Storage {
for (int i = 0; i < cells.size(); i++) { for (int i = 0; i < cells.size(); i++) {
Cell c = cells.get(i); Cell c = cells.get(i);
switch (c.getColumn().getKind()) { switch (c.getColumn().getKind()) {
case Null:
p.setNull(i+2, java.sql.Types.NVARCHAR);
case Binary: case Binary:
p.setBytes(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueBinary()); p.setBytes(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueBinary());
break; break;
@ -103,6 +106,7 @@ public class StorageSqlite3 implements Storage {
case Complex: throw new FHIRException("SQLite runner does not handle complexes"); case Complex: throw new FHIRException("SQLite runner does not handle complexes");
} }
} }
p.execute();
} catch (Exception e) { } catch (Exception e) {
throw new FHIRException(e); throw new FHIRException(e);
} }

View File

@ -18,6 +18,7 @@ import org.hl7.fhir.utilities.json.model.JsonBoolean;
import org.hl7.fhir.utilities.json.model.JsonElement; import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonNumber; import org.hl7.fhir.utilities.json.model.JsonNumber;
import org.hl7.fhir.utilities.json.model.JsonObject; import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.model.JsonProperty;
import org.hl7.fhir.utilities.json.model.JsonString; import org.hl7.fhir.utilities.json.model.JsonString;
import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
@ -35,7 +36,6 @@ public class Validator {
private Boolean needsName; private Boolean needsName;
private String resourceName; private String resourceName;
private List<Column> columns = new ArrayList<Column>();
private String name; private String name;
public Validator(IWorkerContext context, FHIRPathEngine fpe, List<String> prohibitedNames, Boolean arrays, Boolean complexTypes, Boolean needsName) { public Validator(IWorkerContext context, FHIRPathEngine fpe, List<String> prohibitedNames, Boolean arrays, Boolean complexTypes, Boolean needsName) {
@ -52,11 +52,10 @@ public class Validator {
return resourceName; return resourceName;
} }
public List<Column> getColumns() {
return columns;
}
public void checkViewDefinition(String path, JsonObject viewDefinition) { public void checkViewDefinition(String path, JsonObject viewDefinition) {
checkProperties(viewDefinition, path, "url", "identifier", "name", "version", "title", "status", "experimental", "date", "publisher", "contact", "description", "useContext", "copyright", "resource", "constant", "select", "where");
JsonElement nameJ = viewDefinition.get("name"); JsonElement nameJ = viewDefinition.get("name");
if (nameJ == null) { if (nameJ == null) {
if (needsName == null) { if (needsName == null) {
@ -76,9 +75,12 @@ public class Validator {
} }
} }
List<Column> columns = new ArrayList<>();
viewDefinition.setUserData("columns", columns);
JsonElement resourceNameJ = viewDefinition.get("resource"); JsonElement resourceNameJ = viewDefinition.get("resource");
if (resourceNameJ == null) { if (resourceNameJ == null) {
error(path, viewDefinition, "No resource provided", IssueType.REQUIRED); error(path, viewDefinition, "No resource specified", IssueType.REQUIRED);
} else if (!(resourceNameJ instanceof JsonString)) { } else if (!(resourceNameJ instanceof JsonString)) {
error(path, viewDefinition, "resource must be a string", IssueType.INVALID); error(path, viewDefinition, "resource must be a string", IssueType.INVALID);
} else { } else {
@ -102,17 +104,10 @@ public class Validator {
} }
TypeDetails t = new TypeDetails(CollectionStatus.SINGLETON, resourceName); TypeDetails t = new TypeDetails(CollectionStatus.SINGLETON, resourceName);
if (viewDefinition.has("forEach")) {
checkForEach(path, viewDefinition, viewDefinition.get("forEach"), t);
} else if (viewDefinition.has("forEachOrNull")) {
checkForEachOrNull(path, viewDefinition, viewDefinition.get("forEachOrNull"), t);
} else if (viewDefinition.has("unionAll")) {
checkUnion(path, viewDefinition, viewDefinition.get("unionAll"), t);
} else {
i = 0; i = 0;
if (checkAllObjects(path, viewDefinition, "select")) { if (checkAllObjects(path, viewDefinition, "select")) {
for (JsonObject select : viewDefinition.getJsonObjects("select")) { for (JsonObject select : viewDefinition.getJsonObjects("select")) {
checkSelect(path+".select["+i+"]", select, t); columns.addAll(checkSelect(path+".select["+i+"]", select, t));
i++; i++;
} }
if (i == 0) { if (i == 0) {
@ -122,9 +117,11 @@ public class Validator {
} }
} }
} }
}
private void checkSelect(String path, JsonObject select, TypeDetails t) { private List<Column> checkSelect(String path, JsonObject select, TypeDetails t) {
List<Column> columns = new ArrayList<>();
select.setUserData("columns", columns);
checkProperties(select, path, "column", "select", "forEach", "forEachOrNull", "unionAll");
if (select.has("forEach")) { if (select.has("forEach")) {
t = checkForEach(path, select, select.get("forEach"), t); t = checkForEach(path, select, select.get("forEach"), t);
@ -133,24 +130,18 @@ public class Validator {
} }
if (t != null) { if (t != null) {
boolean content = false;
if (select.has("unionAll")) {
content = checkUnion(path, select, select.get("unionAll"), t);
}
if (select.has("column")) { if (select.has("column")) {
JsonElement a = select.get("column"); JsonElement a = select.get("column");
if (!(a instanceof JsonArray)) { if (!(a instanceof JsonArray)) {
error(path+".column", a, "column is not an array", IssueType.INVALID); error(path+".column", a, "column is not an array", IssueType.INVALID);
} else { } else {
content = true;
int i = 0; int i = 0;
for (JsonElement e : ((JsonArray) a)) { for (JsonElement e : ((JsonArray) a)) {
if (!(e instanceof JsonObject)) { if (!(e instanceof JsonObject)) {
error(path+".column["+i+"]", a, "column["+i+"] is a "+e.type().toName()+" not an object", IssueType.INVALID); error(path+".column["+i+"]", a, "column["+i+"] is a "+e.type().toName()+" not an object", IssueType.INVALID);
} else { } else {
checkColumn(path+".column["+i+"]", (JsonObject) e, t); columns.add(checkColumn(path+".column["+i+"]", (JsonObject) e, t));
} }
} }
} }
@ -161,47 +152,98 @@ public class Validator {
if (!(a instanceof JsonArray)) { if (!(a instanceof JsonArray)) {
error(path+".select", a, "select is not an array", IssueType.INVALID); error(path+".select", a, "select is not an array", IssueType.INVALID);
} else { } else {
content = true;
int i = 0; int i = 0;
for (JsonElement e : ((JsonArray) a)) { for (JsonElement e : ((JsonArray) a)) {
if (!(e instanceof JsonObject)) { if (!(e instanceof JsonObject)) {
error(path+".select["+i+"]", e, "select["+i+"] is not an object", IssueType.INVALID); error(path+".select["+i+"]", e, "select["+i+"] is not an object", IssueType.INVALID);
} else { } else {
checkSelect(path+".select["+i+"]", (JsonObject) e, t); columns.addAll(checkSelect(path+".select["+i+"]", (JsonObject) e, t));
} }
} }
} }
} }
if (!content) { if (select.has("unionAll")) {
columns.addAll(checkUnion(path, select, select.get("unionAll"), t));
}
if (columns.isEmpty()) {
error(path, select, "The select has no columns or selects", IssueType.REQUIRED); error(path, select, "The select has no columns or selects", IssueType.REQUIRED);
} else {
checkColumnNamesUnique(select, path, columns);
}
}
return columns;
}
private void checkColumnNamesUnique(JsonObject select, String path, List<Column> columns) {
Set<String> names = new HashSet<>();
for (Column col : columns) {
if (col != null) {
if (!names.contains(col.getName())) {
names.add(col.getName());
} else if (!col.isDuplicateReported()) {
col.setDuplicateReported(true);
error(path, select, "Duplicate Column Name '"+col.getName()+"'", IssueType.BUSINESSRULE);
}
} }
} }
} }
private List<Column> checkUnion(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
private boolean checkUnion(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
JsonElement a = focus.get("unionAll"); JsonElement a = focus.get("unionAll");
if (!(a instanceof JsonArray)) { if (!(a instanceof JsonArray)) {
error(path+".union", a, "union is not an array", IssueType.INVALID); error(path+".unionAll", a, "union is not an array", IssueType.INVALID);
return false; return null;
} else { } else {
List<List<Column>> unionColumns = new ArrayList<>();
int i = 0; int i = 0;
for (JsonElement e : ((JsonArray) a)) { for (JsonElement e : ((JsonArray) a)) {
if (!(e instanceof JsonObject)) { if (!(e instanceof JsonObject)) {
error(path+".union["+i+"]", e, "union["+i+"] is not an object", IssueType.INVALID); error(path+".unionAll["+i+"]", e, "unionAll["+i+"] is not an object", IssueType.INVALID);
} else { } else {
checkSelect(path+".union["+i+"]", (JsonObject) e, t); unionColumns.add(checkSelect(path+".unionAll["+i+"]", (JsonObject) e, t));
} }
i++;
} }
if (i < 2) { if (i < 2) {
warning(path+".union", a, "union should have more than one item"); warning(path+".unionAll", a, "unionAll should have more than one item");
} }
return true; if (unionColumns.size() > 1) {
List<Column> columns = unionColumns.get(0);
for (int ic = 1; ic < unionColumns.size(); ic++) {
String diff = columnDiffs(columns, unionColumns.get(ic));
if (diff != null) {
error(path+".unionAll["+i+"]", ((JsonArray) a).get(ic), "unionAll["+i+"] column definitions do not match: "+diff, IssueType.INVALID);
}
}
a.setUserData("colunms", columns);
return columns;
}
}
return null;
}
private String columnDiffs(List<Column> list1, List<Column> list2) {
if (list1.size() == list2.size()) {
for (int i = 0; i < list1.size(); i++) {
if (list1.get(i) == null || list2.get(i) == null) {
return null; // just suppress any addition errors
}
String diff = list1.get(i).diff(list2.get(i));
if (diff != null) {
return diff+" at #"+i;
}
}
return null;
} else {
return "Column counts differ: "+list1.size()+" vs "+list2.size();
} }
} }
private void checkColumn(String path, JsonObject column, TypeDetails t) { private Column checkColumn(String path, JsonObject column, TypeDetails t) {
checkProperties(column, path, "path", "name", "description", "collection", "type", "tag");
if (!column.has("path")) { if (!column.has("path")) {
error(path, column, "no path found", IssueType.INVALID); error(path, column, "no path found", IssueType.INVALID);
} else { } else {
@ -253,7 +295,7 @@ public class Validator {
// ok, name is sorted! // ok, name is sorted!
if (columnName != null) { if (columnName != null) {
column.setUserData("name", columnName); column.setUserData("name", columnName);
boolean isColl = (td.getCollectionStatus() != CollectionStatus.SINGLETON) || column(columnName) != null; boolean isColl = (td.getCollectionStatus() != CollectionStatus.SINGLETON);
if (column.has("collection")) { if (column.has("collection")) {
JsonElement collectionJ = column.get("collection"); JsonElement collectionJ = column.get("collection");
if (!(collectionJ instanceof JsonBoolean)) { if (!(collectionJ instanceof JsonBoolean)) {
@ -273,8 +315,11 @@ public class Validator {
warning(path, expression, "column appears to be a collection, but this is not allowed in this context"); warning(path, expression, "column appears to be a collection, but this is not allowed in this context");
} }
} }
// ok collection is sorted
Set<String> types = new HashSet<>(); Set<String> types = new HashSet<>();
if (node.isNullSet()) {
types.add("null");
} else {
// ok collection is sorted
for (String type : td.getTypes()) { for (String type : td.getTypes()) {
types.add(simpleType(type)); types.add(simpleType(type));
} }
@ -293,12 +338,13 @@ public class Validator {
error(path+".type", typeJ, "type must be a string", IssueType.INVALID); error(path+".type", typeJ, "type must be a string", IssueType.INVALID);
} }
} }
}
if (types.size() != 1) { if (types.size() != 1) {
error(path, column, "Unable to determine a type (found "+td.describe()+")", IssueType.BUSINESSRULE); error(path, column, "Unable to determine a type (found "+td.describe()+")", IssueType.BUSINESSRULE);
} else { } else {
String type = types.iterator().next(); String type = types.iterator().next();
boolean ok = false; boolean ok = false;
if (!isSimpleType(type)) { if (!isSimpleType(type) && !"null".equals(type)) {
if (complexTypes) { if (complexTypes) {
warning(path, expression, "Column is a complex type. This is not supported in some Runners"); warning(path, expression, "Column is a complex type. This is not supported in some Runners");
} else if (!complexTypes) { } else if (!complexTypes) {
@ -310,28 +356,21 @@ public class Validator {
ok = true; ok = true;
} }
if (ok) { if (ok) {
Column col = column(columnName); Column col = new Column(columnName, isColl, type, kindForType(type));
if (col != null) { column.setUserData("column", col);
if (!col.getType().equals(type)) { return col;
error(path, expression, "Duplicate definition for "+columnName+" has different types ("+col.getType()+" vs "+type+")", IssueType.BUSINESSRULE);
}
if (col.isColl() != isColl) {
error(path, expression, "Duplicate definition for "+columnName+" has different status for collection ("+col.isColl()+" vs "+isColl+")", IssueType.BUSINESSRULE);
}
} else {
columns.add(new Column(columnName, isColl, type, kindForType(type)));
}
} }
} }
} }
} }
} }
} }
return null;
} }
private ColumnKind kindForType(String type) { private ColumnKind kindForType(String type) {
switch (type) { switch (type) {
case "null": return ColumnKind.Null;
case "dateTime": return ColumnKind.DateTime; case "dateTime": return ColumnKind.DateTime;
case "boolean": return ColumnKind.Boolean; case "boolean": return ColumnKind.Boolean;
case "integer": return ColumnKind.Integer; case "integer": return ColumnKind.Integer;
@ -343,15 +382,6 @@ public class Validator {
} }
} }
private Column column(String columnName) {
for (Column t : columns) {
if (t.getName().equalsIgnoreCase(columnName)) {
return t;
}
}
return null;
}
private boolean isSimpleType(String type) { private boolean isSimpleType(String type) {
return Utilities.existsInList(type, "dateTime", "boolean", "integer", "decimal", "string", "base64Binary"); return Utilities.existsInList(type, "dateTime", "boolean", "integer", "decimal", "string", "base64Binary");
} }
@ -433,6 +463,7 @@ public class Validator {
} }
private void checkConstant(String path, JsonObject constant) { private void checkConstant(String path, JsonObject constant) {
checkProperties(constant, path, "name", "valueBase64Binary", "valueBoolean", "valueCanonical", "valueCode", "valueDate", "valueDateTime", "valueDecimal", "valueId", "valueInstant", "valueInteger", "valueInteger64", "valueOid", "valueString", "valuePositiveInt", "valueTime", "valueUnsignedInt", "valueUri", "valueUrl", "valueUuid");
JsonElement nameJ = constant.get("name"); JsonElement nameJ = constant.get("name");
if (nameJ == null) { if (nameJ == null) {
error(path, constant, "No name provided", IssueType.REQUIRED); error(path, constant, "No name provided", IssueType.REQUIRED);
@ -508,6 +539,8 @@ public class Validator {
} }
} }
private void checkWhere(String path, JsonObject where) { private void checkWhere(String path, JsonObject where) {
checkProperties(where, path, "path", "description");
String expr = where.asString("path"); String expr = where.asString("path");
if (expr == null) { if (expr == null) {
error(path, where, "No path provided", IssueType.REQUIRED); error(path, where, "No path provided", IssueType.REQUIRED);
@ -534,6 +567,19 @@ public class Validator {
} }
} }
private void checkProperties(JsonObject obj, String path, String... names) {
for (JsonProperty p : obj.getProperties()) {
boolean nameOk = "extension".equals(p.getName());
for (String name : names) {
nameOk = nameOk || name.equals(p.getName());
}
if (!nameOk) {
error(path+"."+p.getName(), p.getValue(), "Unknown JSON "+p.getValue().type().name(), IssueType.UNKNOWN);
}
}
}
private boolean isValidName(String name) { private boolean isValidName(String name) {
boolean first = true; boolean first = true;
for (char c : name.toCharArray()) { for (char c : name.toCharArray()) {

View File

@ -1,24 +1,37 @@
package org.hl7.fhir.r5.utils.validation; package org.hl7.fhir.r5.utils.validation;
import com.fasterxml.jackson.annotation.JsonProperty;
public class BundleValidationRule { public class BundleValidationRule {
@JsonProperty("rule")
private String rule; private String rule;
@JsonProperty("profile")
private String profile; private String profile;
private boolean checked; private boolean checked;
public BundleValidationRule(String rule, String profile) { @JsonProperty("rule")
super();
this.rule = rule;
this.profile = profile;
}
public String getRule() { public String getRule() {
return rule; return rule;
} }
@JsonProperty("rule")
public BundleValidationRule setRule(String rule) {
this.rule = rule;
return this;
}
@JsonProperty("profile")
public String getProfile() { public String getProfile() {
return profile; return profile;
} }
@JsonProperty("profile")
public BundleValidationRule setProfile(String profile) {
this.profile = profile;
return this;
}
public boolean isChecked() { public boolean isChecked() {
return checked; return checked;
} }

View File

@ -54,7 +54,7 @@ public class SQLOnFhirTests {
} }
@Override @Override
public Base resolveReference(String ref, String rt) { public Base resolveReference(Base rootResource, String ref, String rt) {
if (ref == null) { if (ref == null) {
return null; return null;
} }
@ -126,6 +126,7 @@ public class SQLOnFhirTests {
runner.execute(test.path+".view", test.testCase.getJsonObject("view")); runner.execute(test.path+".view", test.testCase.getJsonObject("view"));
results = store.getRows(); results = store.getRows();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
Assertions.assertTrue(test.testCase.has("expectError"), e.getMessage()); Assertions.assertTrue(test.testCase.has("expectError"), e.getMessage());
} }
if (results != null) { if (results != null) {

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>6.2.1-SNAPSHOT</version> <version>6.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>6.2.1-SNAPSHOT</version> <version>6.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -125,7 +125,7 @@ public class HL7WorkGroups {
case "sd": return "Structured Documents"; case "sd": return "Structured Documents";
case "sec": return "Security"; case "sec": return "Security";
case "soa": return "Services Oriented Architecture"; case "soa": return "Services Oriented Architecture";
case "ti": return "Terminology Infrastructure Work Group"; case "ti": return "Terminology Infrastructure";
case "tsmg": return "Terminology Services Management Group (TSMG)"; case "tsmg": return "Terminology Services Management Group (TSMG)";
case "us": return "US Realm Steering Committee"; case "us": return "US Realm Steering Committee";
case "v2": return "V2 Management Group"; case "v2": return "V2 Management Group";

View File

@ -251,14 +251,10 @@ public class VersionUtilities {
} }
public static boolean isSemVer(String version) { public static boolean isSemVer(String version) {
if (Utilities.charCount(version, '.') != 2) { if (Utilities.noString(version)) {
return false; return false;
} }
String[] p = version.split("\\."); return version.matches("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-\\+]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-\\+][0-9a-zA-Z-\\+]*))*))?$");
if (p[2].contains("-")) {
p[2] = p[2].substring(0, p[2].indexOf("-"));
}
return Utilities.isInteger(p[0]) && Utilities.isInteger(p[1]) && Utilities.isInteger(p[2]);
} }
/** /**

View File

@ -1012,6 +1012,8 @@ public class I18nConstants {
public static final String TERMINOLOGY_TX_OID_MULTIPLE_MATCHES = "TERMINOLOGY_TX_OID_MULTIPLE_MATCHES"; public static final String TERMINOLOGY_TX_OID_MULTIPLE_MATCHES = "TERMINOLOGY_TX_OID_MULTIPLE_MATCHES";
public static final String CDA_UNKNOWN_TEMPLATE = "CDA_UNKNOWN_TEMPLATE"; public static final String CDA_UNKNOWN_TEMPLATE = "CDA_UNKNOWN_TEMPLATE";
public static final String CDA_UNKNOWN_TEMPLATE_EXT = "CDA_UNKNOWN_TEMPLATE_EXT"; public static final String CDA_UNKNOWN_TEMPLATE_EXT = "CDA_UNKNOWN_TEMPLATE_EXT";
public static final String UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV = "UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV";
public static final String ED_CONTEXT_INVARIANT_EXPRESSION_ERROR = "ED_CONTEXT_INVARIANT_EXPRESSION_ERROR";
} }

View File

@ -95,7 +95,7 @@ public abstract class BasePackageCacheManager implements IPackageCacheManager {
// this is not a long term thing, but it's not clear how to release patches for // this is not a long term thing, but it's not clear how to release patches for
// 1.4.0 // 1.4.0
private boolean okToUsePackageServer(String server, String id) { private boolean okToUsePackageServer(String server, String id) {
if ("http://packages.fhir.org".equals(server) && "hl7.fhir.r2b.core".equals(id)) { if (PackageServer.PRIMARY_SERVER.equals(server) && "hl7.fhir.r2b.core".equals(id)) {
return false; return false;
} }
return true; return true;

View File

@ -119,7 +119,6 @@ public class PackageClient {
try { try {
return obj.hasString("date") ? obj.asDate("date") : null; return obj.hasString("date") ? obj.asDate("date") : null;
} catch (DateTimeParseException e) { } catch (DateTimeParseException e) {
//FIXME Some IGs use an older date format:
try { try {
return new SimpleDateFormat("yyyyMMddhhmmss").parse(obj.getJsonString("date").asString()).toInstant(); return new SimpleDateFormat("yyyyMMddhhmmss").parse(obj.getJsonString("date").asString()).toInstant();
} catch (ParseException ex) { } catch (ParseException ex) {

View File

@ -46,7 +46,7 @@ public class PackageServer {
return url; return url;
} }
public static final String PRIMARY_SERVER = "http://packages.fhir.org"; public static final String PRIMARY_SERVER = "https://packages.fhir.org";
public static final String SECONDARY_SERVER = "https://packages2.fhir.org/packages"; public static final String SECONDARY_SERVER = "https://packages2.fhir.org/packages";
public static PackageServer primaryServer() { public static PackageServer primaryServer() {

View File

@ -610,7 +610,7 @@ FHIRPATH_PRIMITIVE_ONLY = Error evaluating FHIRPath expression: The function {0}
FHIRPATH_REFERENCE_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on ordered string, uri, canonical or Reference but found {1} FHIRPATH_REFERENCE_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on ordered string, uri, canonical or Reference but found {1}
FHIRPATH_CODED_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on ordered string, code, uri, Coding, CodeableConcept but found {1} FHIRPATH_CODED_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on ordered string, code, uri, Coding, CodeableConcept but found {1}
FHIRPATH_STRING_ORD_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on ordered collection of string, uri, code, id but found {1} FHIRPATH_STRING_ORD_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on ordered collection of string, uri, code, id but found {1}
FHIRPATH_STRING_SING_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on string, uri, code, id but found {1} FHIRPATH_STRING_SING_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on string, uri, url, code, id but found {1}
FHIRPATH_NO_COLLECTION = Error evaluating FHIRPath expression: The function {0} can only be used on a singleton value but found {1} FHIRPATH_NO_COLLECTION = Error evaluating FHIRPath expression: The function {0} can only be used on a singleton value but found {1}
FHIRPATH_NOT_IMPLEMENTED = Error evaluating FHIRPath expression: The function {0} is not implemented FHIRPATH_NOT_IMPLEMENTED = Error evaluating FHIRPath expression: The function {0} is not implemented
FHIRPATH_PARAM_WRONG = Error evaluating FHIRPath expression: The expression type {0} is not supported for parameter {1} on function {2} FHIRPATH_PARAM_WRONG = Error evaluating FHIRPath expression: The expression type {0} is not supported for parameter {1} on function {2}
@ -928,7 +928,7 @@ NO_VALID_DISPLAY_FOUND_one = No valid Display Names found for {1}#{2} in the lan
NO_VALID_DISPLAY_FOUND_other = No valid Display Names found for {1}#{2} in the languages {4} NO_VALID_DISPLAY_FOUND_other = No valid Display Names found for {1}#{2} in the languages {4}
SD_NO_CONTEXT_WHEN_NOT_EXTENSION = The type is {0} so an extension context should not be specified SD_NO_CONTEXT_WHEN_NOT_EXTENSION = The type is {0} so an extension context should not be specified
SD_NO_CONTEXT_INV_WHEN_NOT_EXTENSION = The type is {0} so an extension context invariants should not be specified SD_NO_CONTEXT_INV_WHEN_NOT_EXTENSION = The type is {0} so an extension context invariants should not be specified
SD_CONTEXT_SHOULD_NOT_BE_ELEMENT = Review the extension type: extensions should not have a context of {0} unless it''s really intended that they can be used anywhere SD_CONTEXT_SHOULD_NOT_BE_ELEMENT = Review the extension type for {1}: extensions should not have a context of {0} unless it''s really intended that they can be used anywhere
ED_PATH_WRONG_TYPE_MATCH = The path must be ''{0}'' not ''{1}'' when the type list is not constrained ED_PATH_WRONG_TYPE_MATCH = The path must be ''{0}'' not ''{1}'' when the type list is not constrained
ATTEMPT_TO_CHANGE_SLICING = The element at {0} defines the slicing {1} but then an element in the slicing {2} tries to redefine the slicing to {3} ATTEMPT_TO_CHANGE_SLICING = The element at {0} defines the slicing {1} but then an element in the slicing {2} tries to redefine the slicing to {3}
REPEAT_SLICING_IGNORED = The element at {0} defines the slicing but then an element in the slicing {2} repeats it, which is ignored REPEAT_SLICING_IGNORED = The element at {0} defines the slicing but then an element in the slicing {2} repeats it, which is ignored
@ -1069,3 +1069,5 @@ XSI_TYPE_UNNECESSARY = xsi:type is unnecessary at this point
TERMINOLOGY_TX_OID_MULTIPLE_MATCHES = The OID ''{0}'' matches multiple code systems ({1}) TERMINOLOGY_TX_OID_MULTIPLE_MATCHES = The OID ''{0}'' matches multiple code systems ({1})
CDA_UNKNOWN_TEMPLATE = The CDA Template {0} is not known CDA_UNKNOWN_TEMPLATE = The CDA Template {0} is not known
CDA_UNKNOWN_TEMPLATE_EXT = The CDA Template {0} / {1} is not known CDA_UNKNOWN_TEMPLATE_EXT = The CDA Template {0} / {1} is not known
UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV = The types could not be determined from the extension context, so the invariant can't be validated (types = {0})
ED_CONTEXT_INVARIANT_EXPRESSION_ERROR = Error in constraint ''{0}'': {1}

View File

@ -11,6 +11,7 @@ public class VersionUtilitiesTest {
public void isValidSemVer() { public void isValidSemVer() {
assertTrue(VersionUtilities.isSemVer("0.1.1")); assertTrue(VersionUtilities.isSemVer("0.1.1"));
assertTrue(VersionUtilities.isSemVer("0.1.1-ballot1")); assertTrue(VersionUtilities.isSemVer("0.1.1-ballot1"));
assertTrue(VersionUtilities.isSemVer("0.0.0-alpha.0.131"));
assertFalse(VersionUtilities.isSemVer("0.1.a")); assertFalse(VersionUtilities.isSemVer("0.1.a"));
} }

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>6.2.1-SNAPSHOT</version> <version>6.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>6.2.1-SNAPSHOT</version> <version>6.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -351,7 +351,6 @@ public class BaseValidator implements IValidationContextResourceLoader {
* Set this parameter to <code>false</code> if the validation does not pass * Set this parameter to <code>false</code> if the validation does not pass
* @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
protected boolean slicingHint(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, boolean isCritical, String msg, String html, String[] text) { protected boolean slicingHint(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, boolean isCritical, String msg, String html, String[] text) {
if (!thePass && doingHints()) { if (!thePass && doingHints()) {
addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.INFORMATION, null).setSlicingHint(true).setSliceHtml(html, text).setCriticalSignpost(isCritical); addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.INFORMATION, null).setSlicingHint(true).setSliceHtml(html, text).setCriticalSignpost(isCritical);

View File

@ -132,7 +132,7 @@ public class CliContext {
@JsonProperty("outputStyle") @JsonProperty("outputStyle")
private String outputStyle = null; private String outputStyle = null;
// TODO: Mark what goes here? @JsonProperty("bundleValidationRules")
private List<BundleValidationRule> bundleValidationRules = new ArrayList<>(); private List<BundleValidationRule> bundleValidationRules = new ArrayList<>();
@JsonProperty("jurisdiction") @JsonProperty("jurisdiction")
@ -191,11 +191,17 @@ public class CliContext {
return this; return this;
} }
// TODO: Mark what goes here? @JsonProperty("bundleValidationRules")
public List<BundleValidationRule> getBundleValidationRules() { public List<BundleValidationRule> getBundleValidationRules() {
return bundleValidationRules; return bundleValidationRules;
} }
@JsonProperty("bundleValidationRules")
public CliContext setBundleValidationRules(List<BundleValidationRule> bundleValidationRules) {
this.bundleValidationRules = bundleValidationRules;
return this;
}
public CliContext addIg(String ig) { public CliContext addIg(String ig) {
if (this.igs == null) { if (this.igs == null) {
this.igs = new ArrayList<>(); this.igs = new ArrayList<>();
@ -322,14 +328,18 @@ public class CliContext {
this.allowDoubleQuotesInFHIRPath = allowDoubleQuotesInFHIRPath; this.allowDoubleQuotesInFHIRPath = allowDoubleQuotesInFHIRPath;
} }
@JsonProperty("checkIPSCodes")
public boolean isCheckIPSCodes() { public boolean isCheckIPSCodes() {
return checkIPSCodes; return checkIPSCodes;
} }
public void setCheckIPSCodes(boolean checkIPSCodes) { @JsonProperty("checkIPSCodes")
public CliContext setCheckIPSCodes(boolean checkIPSCodes) {
this.checkIPSCodes = checkIPSCodes; this.checkIPSCodes = checkIPSCodes;
return this;
} }
@JsonProperty("locale") @JsonProperty("locale")
public String getLanguageCode() { public String getLanguageCode() {
return locale; return locale;

View File

@ -181,25 +181,25 @@ public class Params {
cliContext.addProfile(p); cliContext.addProfile(p);
} }
} else if (args[i].equals(BUNDLE)) { } else if (args[i].equals(BUNDLE)) {
String p = null; String profile = null;
String r = null; String rule = null;
if (i + 1 == args.length) { if (i + 1 == args.length) {
throw new Error("Specified -profile without indicating bundle rule "); throw new Error("Specified -profile without indicating bundle rule ");
} else { } else {
r = args[++i]; rule = args[++i];
} }
if (i + 1 == args.length) { if (i + 1 == args.length) {
throw new Error("Specified -profile without indicating profile source"); throw new Error("Specified -profile without indicating profile source");
} else { } else {
p = args[++i]; profile = args[++i];
} }
cliContext.getBundleValidationRules().add(new BundleValidationRule(r, p)); cliContext.getBundleValidationRules().add(new BundleValidationRule().setRule(rule).setProfile(profile));
} else if (args[i].equals(QUESTIONNAIRE)) { } else if (args[i].equals(QUESTIONNAIRE)) {
if (i + 1 == args.length) if (i + 1 == args.length)
throw new Error("Specified -questionnaire without indicating questionnaire mode"); throw new Error("Specified -questionnaire without indicating questionnaire mode");
else { else {
String q = args[++i]; String questionnaireMode = args[++i];
cliContext.setQuestionnaireMode(QuestionnaireMode.fromCode(q)); cliContext.setQuestionnaireMode(QuestionnaireMode.fromCode(questionnaireMode));
} }
} else if (args[i].equals(LEVEL)) { } else if (args[i].equals(LEVEL)) {
if (i + 1 == args.length) if (i + 1 == args.length)

View File

@ -5871,10 +5871,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!definition.getPath().contains(".") && profile.hasExtension(ToolingExtensions.EXT_PROFILE_STYLE) && "cda".equals(ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_PROFILE_STYLE))) { if (!definition.getPath().contains(".") && profile.hasExtension(ToolingExtensions.EXT_PROFILE_STYLE) && "cda".equals(ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_PROFILE_STYLE))) {
List<Element> templates = element.getChildren("templateId"); List<Element> templates = element.getChildren("templateId");
for (Element t : templates) { for (Element t : templates) {
String tid = "urn:hl7ii:"+t.getChildValue("root")+(t.hasChild("extension") ? ":"+t.getChildValue("extension") : ""); String tid = t.hasChild("extension") ? "urn:hl7ii:"+t.getChildValue("root")+ ":"+t.getChildValue("extension") : "urn:oid:"+t.getChildValue("root");
StructureDefinition sd = cu.fetchProfileByIdentifier(tid); StructureDefinition sd = cu.fetchProfileByIdentifier(tid);
if (sd == null) { if (sd == null) {
ok = rule(errors, "2023-10-20", IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, t.hasChild("extension") ? I18nConstants.CDA_UNKNOWN_TEMPLATE_EXT : I18nConstants.CDA_UNKNOWN_TEMPLATE, t.getChildValue("root"), t.getChildValue("extension")) && ok; hint(errors, "2023-10-20", IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, t.hasChild("extension") ? I18nConstants.CDA_UNKNOWN_TEMPLATE_EXT : I18nConstants.CDA_UNKNOWN_TEMPLATE, t.getChildValue("root"), t.getChildValue("extension"));
} else { } else {
ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); ElementDefinition ed = sd.getSnapshot().getElementFirstRep();
if (!element.hasValidated(sd, ed)) { if (!element.hasValidated(sd, ed)) {

View File

@ -38,6 +38,7 @@ import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.i18n.I18nConstants;
@ -46,6 +47,7 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.validation.BaseValidator; import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.instance.utils.NodeStack; import org.hl7.fhir.validation.instance.utils.NodeStack;
import org.hl7.fhir.validation.instance.utils.ValidationContext;
public class StructureDefinitionValidator extends BaseValidator { public class StructureDefinitionValidator extends BaseValidator {
@ -152,6 +154,7 @@ public class StructureDefinitionValidator extends BaseValidator {
} }
} }
} }
List<Element> extensions = src.getChildren("extension"); List<Element> extensions = src.getChildren("extension");
int c = 0; int c = 0;
for (Element extension : extensions) { for (Element extension : extensions) {
@ -161,6 +164,13 @@ public class StructureDefinitionValidator extends BaseValidator {
c++; c++;
} }
List<Element> contextInvariants = src.getChildren("contextInvariant");
c = 0;
for (Element contextInvariant : contextInvariants) {
ok = validateContextInvariant(errors, contextInvariant, src, stack.push(contextInvariant, c, null, null)) && ok;
c++;
}
// if this is defining an extension, make sure that the extension fixed value matches the URL // if this is defining an extension, make sure that the extension fixed value matches the URL
String type = src.getNamedChildValue("type"); String type = src.getNamedChildValue("type");
if ("Extension".equals(type)) { if ("Extension".equals(type)) {
@ -344,7 +354,7 @@ public class StructureDefinitionValidator extends BaseValidator {
cv = ec.primitiveValue(); cv = ec.primitiveValue();
} }
if ("element".equals(ct) && "Element".equals(cv)) { if ("element".equals(ct) && "Element".equals(cv)) {
warning(errors, "2023-04-23", IssueType.BUSINESSRULE, n.getLiteralPath(), false, I18nConstants.SD_CONTEXT_SHOULD_NOT_BE_ELEMENT, cv); warning(errors, "2023-04-23", IssueType.BUSINESSRULE, n.getLiteralPath(), false, I18nConstants.SD_CONTEXT_SHOULD_NOT_BE_ELEMENT, cv, src.getNamedChildValue("id"));
} }
} else { } else {
ok = rule(errors, "2023-04-23", IssueType.INVALID, n.getLiteralPath(), false, I18nConstants.SD_NO_CONTEXT_WHEN_NOT_EXTENSION, type) && ok; ok = rule(errors, "2023-04-23", IssueType.INVALID, n.getLiteralPath(), false, I18nConstants.SD_NO_CONTEXT_WHEN_NOT_EXTENSION, type) && ok;
@ -545,10 +555,11 @@ public class StructureDefinitionValidator extends BaseValidator {
types.add(elements.get(0).getNamedChildValue("path")); types.add(elements.get(0).getNamedChildValue("path"));
} }
List<String> warnings = new ArrayList<>(); List<String> warnings = new ArrayList<>();
ValidationContext vc = new ValidationContext(invariant);
if (Utilities.existsInList(rootPath, context.getResourceNames())) { if (Utilities.existsInList(rootPath, context.getResourceNames())) {
fpe.checkOnTypes(invariant, rootPath, types, fpe.parse(exp), warnings); fpe.checkOnTypes(vc, rootPath, types, fpe.parse(exp), warnings);
} else { } else {
fpe.checkOnTypes(invariant, "DomainResource", types, fpe.parse(exp), warnings); fpe.checkOnTypes(vc, "DomainResource", types, fpe.parse(exp), warnings);
} }
for (String s : warnings) { for (String s : warnings) {
warning(errors, "2023-07-27", IssueType.BUSINESSRULE, stack, false, key+": "+s); warning(errors, "2023-07-27", IssueType.BUSINESSRULE, stack, false, key+": "+s);
@ -571,6 +582,61 @@ public class StructureDefinitionValidator extends BaseValidator {
return ok; return ok;
} }
private boolean validateContextInvariant(List<ValidationMessage> errors, Element invariant, Element sd, NodeStack stack) {
boolean ok = true;
String expression = invariant.getValue();
if (!Utilities.noString(expression)) {
// we have to figure out the context, and we might be in type slicing.
String exp = expression;
List<String> types = listTypeContexts(sd);
if (types.size() == 0) {
hint(errors, "2023-10-31", IssueType.INFORMATIONAL, stack, false, I18nConstants.UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV, listContexts(sd));
} else
try {
List<String> warnings = new ArrayList<>();
ValidationContext vc = new ValidationContext(invariant);
fpe.checkOnTypes(vc, "DomainResource", types, fpe.parse(exp), warnings);
for (String s : warnings) {
warning(errors, "2023-07-27", IssueType.BUSINESSRULE, stack, false, s);
}
} catch (Exception e) {
if (debug) {
e.printStackTrace();
}
ok = rule(errors, "2023-06-19", IssueType.INVALID, stack, false, I18nConstants.ED_CONTEXT_INVARIANT_EXPRESSION_ERROR, expression, e.getMessage()) && ok;
}
}
return ok;
}
private Object listContexts(Element sd) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (Element e : sd.getChildren("context")) {
b.append(e.getNamedChildValue("type")+"="+e.getNamedChildValue("expression"));
}
return b.toString();
}
private List<String> listTypeContexts(Element sd) {
List<String> types = new ArrayList<>();
for (Element e : sd.getChildren("context")) {
switch (e.getNamedChildValue("type")) {
case "fhirpath" :
break;
case "element" :
types.add(e.getNamedChildValue("expression"));
break;
case "extension" :
// this isn't defined?
types.add(e.getNamedChildValue("Extension"));
types.add(e.getNamedChildValue("Extension.value"));
break;
default:
}
}
return types;
}
private boolean haseHasInvariant(StructureDefinition base, String key) { private boolean haseHasInvariant(StructureDefinition base, String key) {
for (ElementDefinition ed : base.getSnapshot().getElement()) { for (ElementDefinition ed : base.getSnapshot().getElement()) {
for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {

View File

@ -287,7 +287,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
} }
val.getBundleValidationRules().clear(); val.getBundleValidationRules().clear();
if (content.has("bundle-param")) { if (content.has("bundle-param")) {
val.getBundleValidationRules().add(new BundleValidationRule(content.getAsJsonObject("bundle-param").get("rule").getAsString(), content.getAsJsonObject("bundle-param").get("profile").getAsString())); val.getBundleValidationRules().add(new BundleValidationRule().setRule(content.getAsJsonObject("bundle-param").get("rule").getAsString()).setProfile( content.getAsJsonObject("bundle-param").get("profile").getAsString()));
} }
if (content.has("profiles")) { if (content.has("profiles")) {
for (JsonElement je : content.getAsJsonArray("profiles")) { for (JsonElement je : content.getAsJsonArray("profiles")) {

View File

@ -14,13 +14,13 @@
HAPI FHIR HAPI FHIR
--> -->
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>6.2.1-SNAPSHOT</version> <version>6.2.2-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<properties> <properties>
<guava_version>32.0.1-jre</guava_version> <guava_version>32.0.1-jre</guava_version>
<hapi_fhir_version>6.4.1</hapi_fhir_version> <hapi_fhir_version>6.4.1</hapi_fhir_version>
<validator_test_case_version>1.4.13</validator_test_case_version> <validator_test_case_version>1.4.14</validator_test_case_version>
<jackson_version>2.15.2</jackson_version> <jackson_version>2.15.2</jackson_version>
<junit_jupiter_version>5.9.2</junit_jupiter_version> <junit_jupiter_version>5.9.2</junit_jupiter_version>
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version> <junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>