Merge remote-tracking branch 'origin/master' into do-20240830-maven-compiler-update

This commit is contained in:
dotasek 2024-09-12 10:50:41 -04:00
commit 9fb7c83e40
41 changed files with 613 additions and 313 deletions

View File

@ -1,7 +1,18 @@
## Validator Changes
* no changes
* Fix expression for con-3 properly (fix validation problem on some condition resources)
* Fix FHIRPath bug using wrong type on simple elements when checking FHIRPath types
* FHIRPath: Allow _ in constant names (per FHIRPath spec)
* Fix value set rendering creating wrong references
* Fix bug processing value set includes / excludes that are just value sets (no system value)
* Alter processing of unknown code systems per discussion at ,https://chat.fhir.org/#narrow/stream/179252-IG-creation/topic/Don't.20error.20when.20you.20can't.20find.20code.20system and implement unknown-codesystems-cause-errors
* Improve message for when elements are out of order in profile differentials
## Other code changes
* no changes
* fix problem where profile rendering had spurious 'slices for' nodes everywhere
* Update SQL-On-FHIR implementation for latest cases, and clone test cases to general test care repository
* Fix problem generating value set spreadsheets
* fix concurrent modification error processing language translations
* Check for null fetcher processing ConceptMaps (#1728)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -289,7 +289,7 @@ public class ProfilePathProcessor {
start++;
} else {
// we're just going to accept the differential slicing at face value
ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy());
ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy(), true);
outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource()));
profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl());
@ -667,14 +667,14 @@ public class ProfilePathProcessor {
// some of what's in currentBase overrides template
template = profileUtilities.fillOutFromBase(template, currentBase);
ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), template);
ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), template, true);
outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource()));
res = outcome;
profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl());
if (diffMatches.get(0).hasSliceName()) {
template = currentBase.copy();
template = profileUtilities.updateURLs(getUrl(), getWebUrl(), template);
template = profileUtilities.updateURLs(getUrl(), getWebUrl(), template, true);
template.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), template.getPath(), getRedirector(), getContextPathSource()));
checkToSeeIfSlicingExists(diffMatches.get(0), template);
@ -866,13 +866,13 @@ public class ProfilePathProcessor {
private void processSimplePathWithEmptyDiffMatches(ElementDefinition currentBase, String currentBasePath, List<ElementDefinition> diffMatches, ProfilePathProcessorState cursors, MappingAssistant mapHelper) {
ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy());
ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy(), true);
outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource()));
profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl());
profileUtilities.updateConstraintSources(outcome, getSourceStructureDefinition().getUrl());
profileUtilities.checkExtensions(outcome);
profileUtilities.updateFromObligationProfiles(outcome);
profileUtilities.updateURLs(url, webUrl, outcome);
profileUtilities.updateURLs(url, webUrl, outcome, true);
profileUtilities.markDerived(outcome);
if (cursors.resultPathBase == null)
cursors.resultPathBase = outcome.getPath();
@ -1033,7 +1033,7 @@ public class ProfilePathProcessor {
if (!currentBase.isChoice() && !profileUtilities.ruleMatches(dSlice.getRules(), bSlice.getRules()))
throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.SLICING_RULES_ON_DIFFERENTIAL__DO_NOT_MATCH_THOSE_ON_BASE___RULE___, profileUtilities.summarizeSlicing(dSlice), profileUtilities.summarizeSlicing(bSlice), path, cursors.contextName));
}
ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy());
ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy(), true);
outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource()));
profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl());
if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) {
@ -1095,7 +1095,7 @@ public class ProfilePathProcessor {
// We need to copy children of the backbone element before we start messing around with slices
int newBaseLimit = profileUtilities.findEndOfElement(cursors.base, cursors.baseCursor);
for (int i = cursors.baseCursor + 1; i <= newBaseLimit; i++) {
outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), cursors.base.getElement().get(i).copy());
outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), cursors.base.getElement().get(i).copy(), true);
outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource()));
debugCheck(outcome);
getResult().getElement().add(outcome);
@ -1106,7 +1106,7 @@ public class ProfilePathProcessor {
List<ElementDefinition> baseMatches = profileUtilities.getSiblings(cursors.base.getElement(), currentBase);
for (ElementDefinition baseItem : baseMatches) {
cursors.baseCursor = cursors.base.getElement().indexOf(baseItem);
outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), baseItem.copy());
outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), baseItem.copy(), true);
profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl());
outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource()));
outcome.setSlicing(null);
@ -1139,7 +1139,7 @@ public class ProfilePathProcessor {
cursors.baseCursor++;
// just copy any children on the base
while (cursors.baseCursor < cursors.base.getElement().size() && cursors.base.getElement().get(cursors.baseCursor).getPath().startsWith(path) && !cursors.base.getElement().get(cursors.baseCursor).getPath().equals(path)) {
outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), cursors.base.getElement().get(cursors.baseCursor).copy());
outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), cursors.base.getElement().get(cursors.baseCursor).copy(), true);
outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource()));
if (!outcome.getPath().startsWith(cursors.resultPathBase))
throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH));
@ -1166,7 +1166,7 @@ public class ProfilePathProcessor {
for (ElementDefinition baseItem : baseMatches)
if (baseItem.getSliceName().equals(diffItem.getSliceName()))
throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.NAMED_ITEMS_ARE_OUT_OF_ORDER_IN_THE_SLICE));
outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy());
outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy(), true);
// outcome = updateURLs(url, diffItem.copy());
outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource()));
profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl());
@ -1409,7 +1409,7 @@ public class ProfilePathProcessor {
private void processPathWithSlicedBaseAndEmptyDiffMatches(ElementDefinition currentBase, String currentBasePath, List<ElementDefinition> diffMatches, ProfilePathProcessorState cursors, String path, MappingAssistant mapHelper) {
if (profileUtilities.hasInnerDiffMatches(getDifferential(), path, cursors.diffCursor, getDiffLimit(), cursors.base.getElement(), true)) {
// so we just copy it in
ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy());
ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), currentBase.copy(), true);
outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource()));
profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl());
profileUtilities.markDerived(outcome);
@ -1457,7 +1457,7 @@ public class ProfilePathProcessor {
// the differential doesn't say anything about this item
// copy across the currentbase, and all of its children and siblings
while (cursors.baseCursor < cursors.base.getElement().size() && cursors.base.getElement().get(cursors.baseCursor).getPath().startsWith(path)) {
ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), cursors.base.getElement().get(cursors.baseCursor).copy());
ElementDefinition outcome = profileUtilities.updateURLs(getUrl(), getWebUrl(), cursors.base.getElement().get(cursors.baseCursor).copy(), true);
outcome.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), outcome.getPath(), getRedirector(), getContextPathSource()));
if (!outcome.getPath().startsWith(cursors.resultPathBase))
throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH_IN_PROFILE___VS_, getProfileName(), outcome.getPath(), cursors.resultPathBase));

View File

@ -739,7 +739,7 @@ public class ProfileUtilities {
if (existing != null) {
updateFromDefinition(existing, e, profileName, false, url, base, derived, "StructureDefinition.differential.element["+i+"]", mappingDetails);
} else {
ElementDefinition outcome = updateURLs(url, webUrl, e.copy());
ElementDefinition outcome = updateURLs(url, webUrl, e.copy(), true);
e.setUserData(UD_GENERATED_IN_SNAPSHOT, outcome);
derived.getSnapshot().addElement(outcome);
if (walksInto(diff.getElement(), e)) {
@ -1042,7 +1042,7 @@ public class ProfileUtilities {
// don't do this. should already be in snapshot ... addInheritedElementsForSpecialization(snapshot, focus, sd.getBaseDefinition(), path, url, weburl);
for (ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.getPath().contains(".")) {
ElementDefinition outcome = updateURLs(url, weburl, ed.copy());
ElementDefinition outcome = updateURLs(url, weburl, ed.copy(), true);
outcome.setPath(outcome.getPath().replace(sd.getTypeName(), path));
snapshot.getElement().add(outcome);
} else {
@ -1548,7 +1548,6 @@ public class ProfileUtilities {
protected void removeStatusExtensions(ElementDefinition outcome) {
outcome.removeExtension(ToolingExtensions.EXT_FMM_LEVEL);
outcome.removeExtension(ToolingExtensions.EXT_FMM_SUPPORT);
outcome.removeExtension(ToolingExtensions.EXT_FMM_DERIVED);
outcome.removeExtension(ToolingExtensions.EXT_STANDARDS_STATUS);
outcome.removeExtension(ToolingExtensions.EXT_NORMATIVE_VERSION);
outcome.removeExtension(ToolingExtensions.EXT_WORKGROUP);
@ -1911,7 +1910,7 @@ public class ProfileUtilities {
* @param element - the Element to update
* @return - the updated Element
*/
public ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) {
public ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element, boolean processRelatives) {
if (element != null) {
ElementDefinition defn = element;
if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#"))
@ -1929,24 +1928,24 @@ public class ProfileUtilities {
if (webUrl != null) {
// also, must touch up the markdown
if (element.hasDefinition()) {
element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
}
if (element.hasComment()) {
element.setComment(processRelativeUrls(element.getComment(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
element.setComment(processRelativeUrls(element.getComment(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
}
if (element.hasRequirements()) {
element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
}
if (element.hasMeaningWhenMissing()) {
element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
}
if (element.hasBinding() && element.getBinding().hasDescription()) {
element.getBinding().setDescription(processRelativeUrls(element.getBinding().getDescription(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
element.getBinding().setDescription(processRelativeUrls(element.getBinding().getDescription(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
}
for (Extension ext : element.getExtension()) {
if (ext.hasValueMarkdownType()) {
MarkdownType md = ext.getValueMarkdownType();
md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
}
}
}
@ -2371,7 +2370,6 @@ public class ProfileUtilities {
if (elist.size() == 2) {
dest.getExtension().remove(elist.get(1));
}
updateExtensionsFromDefinition(dest, source);
for (ElementDefinition ed : obligationProfileElements) {
@ -2423,6 +2421,9 @@ public class ProfileUtilities {
if (e.hasDefinition()) {
base.setDefinition(processRelativeUrls(e.getDefinition(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true));
}
if (e.getBinding().hasDescription()) {
base.getBinding().setDescription(processRelativeUrls(e.getBinding().getDescription(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true));
}
base.setShort(e.getShort());
if (e.hasCommentElement())
base.setCommentElement(e.getCommentElement());
@ -2466,9 +2467,9 @@ public class ProfileUtilities {
if (derived.hasDefinitionElement()) {
if (derived.getDefinition().startsWith("..."))
base.setDefinition(Utilities.appendDerivedTextToBase(base.getDefinition(), derived.getDefinition()));
else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false))
else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) {
base.setDefinitionElement(derived.getDefinitionElement().copy());
else if (trimDifferential)
} else if (trimDifferential)
derived.setDefinitionElement(null);
else if (derived.hasDefinitionElement())
derived.getDefinitionElement().setUserData(UD_DERIVATION_EQUALS, true);

View File

@ -934,10 +934,14 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
throw new Error(formatMessage(I18nConstants.NO_VALUE_SET_IN_URL));
}
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
codeSystemsUsed.add(inc.getSystem());
if (inc.hasSystem()) {
codeSystemsUsed.add(inc.getSystem());
}
}
for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
codeSystemsUsed.add(inc.getSystem());
if (inc.hasSystem()) {
codeSystemsUsed.add(inc.getSystem());
}
}
CacheToken cacheToken = txCache.generateExpandToken(vs, hierarchical);

View File

@ -366,7 +366,11 @@ public class ContextUtilities implements ProfileKnowledgeProvider {
public StructureDefinition fetchByJsonName(String key) {
for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
ElementDefinition ed = sd.getSnapshot().getElementFirstRep();
if (sd.getKind() == StructureDefinitionKind.LOGICAL && ed != null && ed.hasExtension(ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_NAME_DEPRECATED) &&
if (/*sd.getKind() == StructureDefinitionKind.LOGICAL && */
// this is turned off because it's valid to use a FHIR type directly in
// an extension of this kind, and that can't be a logical model. Any profile on
// a type is acceptable as long as it has the json name on it
ed != null && ed.hasExtension(ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_NAME_DEPRECATED) &&
key.equals(ToolingExtensions.readStringExtension(ed, ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_NAME_DEPRECATED))) {
return sd;
}

View File

@ -187,7 +187,7 @@ public class FHIRLexer {
cursor++;
} else
while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') ||
(source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-'))
(source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-' || source.charAt(cursor) == '_'))
cursor++;
current = source.substring(currentStart, cursor);
} else if (ch == '/') {

View File

@ -773,6 +773,25 @@ public class FHIRPathEngine {
return execute(new ExecutionContext(null, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true);
}
/**
* evaluate a path and return the matching elements
*
* @param base - the object against which the path is being evaluated
* @param ExpressionNode - the parsed ExpressionNode statement to use
* @return
* @throws FHIRException
* @
*/
public List<Base> evaluate(Object appContext, Base base, ExpressionNode ExpressionNode) throws FHIRException {
List<Base> list = new ArrayList<Base>();
if (base != null) {
list.add(base);
}
log = new StringBuilder();
return execute(new ExecutionContext(appContext, base != null && base.isResource() ? base : null, base != null && base.isResource() ? base : null, base, base), list, ExpressionNode, true);
}
/**
* evaluate a path and return the matching elements
*
@ -3741,6 +3760,8 @@ public class FHIRPathEngine {
}
if ((focus.hasType("date") || focus.hasType("datetime") || focus.hasType("instant"))) {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal, TypeDetails.FP_DateTime);
} else if ((focus.hasType("time"))) {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Time, TypeDetails.FP_Time);
} else if (focus.hasType("decimal") || focus.hasType("integer")) {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Decimal);
} else {
@ -6351,7 +6372,7 @@ public class FHIRPathEngine {
}
result.addTypes(worker.getResourceNames());
} else {
pt = new ProfiledType(t.getCode());
pt = new ProfiledType(t.getWorkingCode());
}
if (pt != null) {
if (t.hasProfile()) {

View File

@ -3431,7 +3431,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
sdMapCache.put(url, sdCache);
String webroot = sd.getUserString("webroot");
for (ElementDefinition e : sd.getSnapshot().getElement()) {
context.getProfileUtilities().updateURLs(sd.getUrl(), webroot, e);
context.getProfileUtilities().updateURLs(sd.getUrl(), webroot, e, false);
sdCache.put(e.getId(), e);
}
}

View File

@ -754,16 +754,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
if (ref == null) {
ref = (String) cs.getWebPath();
}
if (ref == null && cs.hasUserData("webroot")) {
ref = (String) cs.getUserData("webroot");
}
if (ref == null) {
return "?ngen-14?.html";
}
if (!ref.contains(".html")) {
ref = ref + ".html";
}
return ref.replace("\\", "/");
return ref == null ? null : ref.replace("\\", "/");
}
private void scanForDesignations(ValueSetExpansionContainsComponent c, List<String> langs, Map<String, String> designations) {
@ -922,14 +913,18 @@ public class ValueSetRenderer extends TerminologyRenderer {
td.addText(code);
} else {
String href = context.fixReference(getCsRef(e));
if (href.contains("#"))
href = href + "-"+Utilities.nmtokenize(code);
else
href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(code);
if (isAbstract)
td.ah(context.prefixLocalHref(href)).setAttribute("title", context.formatPhrase(RenderingContext.VS_ABSTRACT_CODE_HINT)).i().addText(code);
else
td.ah(context.prefixLocalHref(href)).addText(code);
if (href == null) {
td.code().tx(code);
} else {
if (href.contains("#"))
href = href + "-"+Utilities.nmtokenize(code);
else
href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(code);
if (isAbstract)
td.ah(context.prefixLocalHref(href)).setAttribute("title", context.formatPhrase(RenderingContext.VS_ABSTRACT_CODE_HINT)).i().addText(code);
else
td.ah(context.prefixLocalHref(href)).addText(code);
}
}
}
@ -1247,11 +1242,15 @@ public class ValueSetRenderer extends TerminologyRenderer {
wli.tx(f.getProperty()+" "+describe(f.getOp())+" ");
if (e != null && codeExistsInValueSet(e, f.getValue())) {
String href = getContext().fixReference(getCsRef(e));
if (href.contains("#"))
href = href + "-"+Utilities.nmtokenize(f.getValue());
else
href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(f.getValue());
wli.ah(context.prefixLocalHref(href)).addText(f.getValue());
if (href == null) {
wli.code().tx(f.getValue());
} else {
if (href.contains("#"))
href = href + "-"+Utilities.nmtokenize(f.getValue());
else
href = href + "#"+e.getId()+"-"+Utilities.nmtokenize(f.getValue());
wli.ah(context.prefixLocalHref(href)).addText(f.getValue());
}
} else if (inc.hasSystem()) {
wli.addText(f.getValue());
ValidationResult vr = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions(), inc.getSystem(), inc.getVersion(), f.getValue(), null);

View File

@ -554,6 +554,9 @@ public class RenderingContext extends RenderingI18nContext {
}
public String fixReference(String ref) {
if (ref == null) {
return null;
}
if (!Utilities.isAbsoluteUrl(ref)) {
return (localPrefix == null ? "" : localPrefix)+ref;
}

View File

@ -606,33 +606,35 @@ public class ValueSetExpander extends ValueSetProcessBase {
excludeCodes(wc, importValueSetForExclude(wc, imp.getValue(), exp, expParams, false, vs).getExpansion());
}
CodeSystem cs = context.fetchSupplementedCodeSystem(exc.getSystem());
if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem(), opContext.getOptions().getFhirVersion())) {
ValueSetExpansionOutcome vse = context.expandVS(exc, false, false);
ValueSet valueset = vse.getValueset();
if (valueset == null)
throw failTSE("Error Expanding ValueSet: "+vse.getError());
excludeCodes(wc, valueset.getExpansion());
return;
}
for (ConceptReferenceComponent c : exc.getConcept()) {
excludeCode(wc, exc.getSystem(), c.getCode());
}
if (exc.getFilter().size() > 0) {
if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
addFragmentWarning(exp, cs);
if (exc.hasSystem()) {
CodeSystem cs = context.fetchSupplementedCodeSystem(exc.getSystem());
if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem(), opContext.getOptions().getFhirVersion())) {
ValueSetExpansionOutcome vse = context.expandVS(exc, false, false);
ValueSet valueset = vse.getValueset();
if (valueset == null)
throw failTSE("Error Expanding ValueSet: "+vse.getError());
excludeCodes(wc, valueset.getExpansion());
return;
}
List<WorkingContext> filters = new ArrayList<>();
for (int i = 1; i < exc.getFilter().size(); i++) {
WorkingContext wc1 = new WorkingContext();
filters.add(wc1);
processFilter(exc, exp, expParams, null, cs, false, exc.getFilter().get(i), wc1, null, true);
for (ConceptReferenceComponent c : exc.getConcept()) {
excludeCode(wc, exc.getSystem(), c.getCode());
}
if (exc.getFilter().size() > 0) {
if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
addFragmentWarning(exp, cs);
}
List<WorkingContext> filters = new ArrayList<>();
for (int i = 1; i < exc.getFilter().size(); i++) {
WorkingContext wc1 = new WorkingContext();
filters.add(wc1);
processFilter(exc, exp, expParams, null, cs, false, exc.getFilter().get(i), wc1, null, true);
}
ConceptSetFilterComponent fc = exc.getFilter().get(0);
WorkingContext wc1 = dwc;
processFilter(exc, exp, expParams, null, cs, false, fc, wc1, filters, true);
}
ConceptSetFilterComponent fc = exc.getFilter().get(0);
WorkingContext wc1 = dwc;
processFilter(exc, exp, expParams, null, cs, false, fc, wc1, filters, true);
}
}
@ -728,7 +730,6 @@ public class ValueSetExpander extends ValueSetProcessBase {
expParams = makeDefaultExpansion();
altCodeParams.seeParameters(expParams);
altCodeParams.seeValueSet(source);
source.checkNoModifiers("ValueSet", "expanding");
focus = source.copy();
focus.setIdBase(null);

View File

@ -125,10 +125,8 @@ public class ValueSetValidator extends ValueSetProcessBase {
public String getMessage() {
return message;
}
}
private ValueSet valueset;
private Map<String, ValueSetValidator> inner = new HashMap<>();
private ValidationOptions options;

View File

@ -103,7 +103,7 @@ public class Runner implements IEvaluationContext {
for (JsonObject w : vd.getJsonObjects("where")) {
String expr = w.asString("path");
ExpressionNode node = fpe.parse(expr);
boolean pass = fpe.evaluateToBoolean(null, b, b, b, node);
boolean pass = fpe.evaluateToBoolean(vd, b, b, b, node);
if (!pass) {
ok = false;
break;
@ -114,7 +114,7 @@ public class Runner implements IEvaluationContext {
rows.add(new ArrayList<Cell>());
for (JsonObject select : vd.getJsonObjects("select")) {
executeSelect(select, b, rows);
executeSelect(vd, select, b, rows);
}
for (List<Cell> row : rows) {
storage.addRow(store, row);
@ -124,14 +124,14 @@ public class Runner implements IEvaluationContext {
storage.finish(store);
}
private void executeSelect(JsonObject select, Base b, List<List<Cell>> rows) {
private void executeSelect(JsonObject vd, JsonObject select, Base b, List<List<Cell>> rows) {
List<Base> focus = new ArrayList<>();
if (select.has("forEach")) {
focus.addAll(executeForEach(select, b));
focus.addAll(executeForEach(vd, select, b));
} else if (select.has("forEachOrNull")) {
focus.addAll(executeForEachOrNull(select, b));
focus.addAll(executeForEachOrNull(vd, select, b));
if (focus.isEmpty()) {
List<Column> columns = (List<Column>) select.getUserData("columns");
for (List<Cell> row : rows) {
@ -159,20 +159,20 @@ public class Runner implements IEvaluationContext {
List<List<Cell>> rowsToAdd = cloneRows(tempRows);
for (JsonObject column : select.getJsonObjects("column")) {
executeColumn(column, f, rowsToAdd);
executeColumn(vd, column, f, rowsToAdd);
}
for (JsonObject sub : select.getJsonObjects("select")) {
executeSelect(sub, f, rowsToAdd);
executeSelect(vd, sub, f, rowsToAdd);
}
executeUnionAll(select.getJsonObjects("unionAll"), f, rowsToAdd);
executeUnionAll(vd, select.getJsonObjects("unionAll"), f, rowsToAdd);
rows.addAll(rowsToAdd);
}
}
private void executeUnionAll(List<JsonObject> unionList, Base b, List<List<Cell>> rows) {
private void executeUnionAll(JsonObject vd, List<JsonObject> unionList, Base b, List<List<Cell>> rows) {
if (unionList.isEmpty()) {
return;
}
@ -183,7 +183,7 @@ public class Runner implements IEvaluationContext {
for (JsonObject union : unionList) {
List<List<Cell>> tempRows = new ArrayList<>();
tempRows.addAll(sourceRows);
executeSelect(union, b, tempRows);
executeSelect(vd, union, b, tempRows);
rows.addAll(tempRows);
}
}
@ -204,25 +204,25 @@ public class Runner implements IEvaluationContext {
return list;
}
private List<Base> executeForEach(JsonObject focus, Base b) {
private List<Base> executeForEach(JsonObject vd, JsonObject focus, Base b) {
ExpressionNode n = (ExpressionNode) focus.getUserData("forEach");
List<Base> result = new ArrayList<>();
result.addAll(fpe.evaluate(b, n));
result.addAll(fpe.evaluate(vd, b, n));
return result;
}
private List<Base> executeForEachOrNull(JsonObject focus, Base b) {
private List<Base> executeForEachOrNull(JsonObject vd, JsonObject focus, Base b) {
ExpressionNode n = (ExpressionNode) focus.getUserData("forEachOrNull");
List<Base> result = new ArrayList<>();
result.addAll(fpe.evaluate(b, n));
result.addAll(fpe.evaluate(vd, b, n));
return result;
}
private void executeColumn(JsonObject column, Base b, List<List<Cell>> rows) {
private void executeColumn(JsonObject vd, JsonObject column, Base b, List<List<Cell>> rows) {
ExpressionNode n = (ExpressionNode) column.getUserData("path");
List<Base> bl2 = new ArrayList<>();
if (b != null) {
bl2.addAll(fpe.evaluate(b, n));
bl2.addAll(fpe.evaluate(vd, b, n));
}
Column col = (Column) column.getUserData("column");
if (col == null) {
@ -344,14 +344,43 @@ public class Runner implements IEvaluationContext {
@Override
public List<Base> resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException {
throw new Error("Not implemented yet: resolveConstant");
List<Base> list = new ArrayList<Base>();
if (explicitConstant) {
JsonObject vd = (JsonObject) appContext;
JsonObject constant = findConstant(vd, name);
if (constant != null) {
Base b = (Base) constant.getUserData("value");
if (b != null) {
list.add(b);
}
}
}
return list;
}
@Override
public TypeDetails resolveConstantType(FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant) throws PathEngineException {
throw new Error("Not implemented yet: resolveConstantType");
if (explicitConstant) {
JsonObject vd = (JsonObject) appContext;
JsonObject constant = findConstant(vd, name.substring(1));
if (constant != null) {
Base b = (Base) constant.getUserData("value");
if (b != null) {
return new TypeDetails(CollectionStatus.SINGLETON, b.fhirType());
}
}
}
return null;
}
private JsonObject findConstant(JsonObject vd, String name) {
for (JsonObject o : vd.getJsonObjects("constant")) {
if (name.equals(o.asString("name"))) {
return o;
}
}
return null;
}
@Override
public boolean log(String argument, List<Base> focus) {
throw new Error("Not implemented yet: log");

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.r5.utils.sql;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonBoolean;
@ -33,16 +34,16 @@ public class StorageJson implements Storage {
JsonObject row = new JsonObject();
rows.add(row);
for (Cell cell : cells) {
if (cell.getValues().size() == 0) {
row.add(cell.getColumn().getName(), new JsonNull());
} else if (cell.getValues().size() == 1) {
row.add(cell.getColumn().getName(), makeJsonNode(cell.getValues().get(0)));
} else {
if (cell.getColumn().isColl() || cell.getValues().size() > 1) {
JsonArray arr = new JsonArray();
row.add(cell.getColumn().getName(), arr);
for (Value value : cell.getValues()) {
arr.add(makeJsonNode(value));
}
} else if (cell.getValues().size() == 0) {
row.add(cell.getColumn().getName(), new JsonNull());
} else {
row.add(cell.getColumn().getName(), makeJsonNode(cell.getValues().get(0)));
}
}
}
@ -87,7 +88,7 @@ public class StorageJson implements Storage {
@Override
public String getKeyForSourceResource(Base res) {
return res.getIdBase();
return res.fhirType()+"/"+res.getIdBase();
}
@Override

View File

@ -11,6 +11,27 @@ import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.fhirpath.ExpressionNode;
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
import org.hl7.fhir.r5.fhirpath.TypeDetails;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.model.Base64BinaryType;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.DateTimeType;
import org.hl7.fhir.r5.model.DateType;
import org.hl7.fhir.r5.model.DecimalType;
import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.InstantType;
import org.hl7.fhir.r5.model.Integer64Type;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.OidType;
import org.hl7.fhir.r5.model.PositiveIntType;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.TimeType;
import org.hl7.fhir.r5.model.UnsignedIntType;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.UrlType;
import org.hl7.fhir.r5.model.UuidType;
import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus;
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IssueMessage;
import org.hl7.fhir.utilities.Utilities;
@ -99,7 +120,7 @@ public class Validator {
i = 0;
if (checkAllObjects(path, viewDefinition, "where")) {
for (JsonObject where : viewDefinition.getJsonObjects("where")) {
checkWhere(path+".where["+i+"]", where);
checkWhere(viewDefinition, path+".where["+i+"]", where);
i++;
}
}
@ -108,7 +129,7 @@ public class Validator {
i = 0;
if (checkAllObjects(path, viewDefinition, "select")) {
for (JsonObject select : viewDefinition.getJsonObjects("select")) {
columns.addAll(checkSelect(path+".select["+i+"]", select, t));
columns.addAll(checkSelect(viewDefinition, path+".select["+i+"]", select, t));
i++;
}
if (i == 0) {
@ -119,15 +140,15 @@ public class Validator {
}
}
private List<Column> checkSelect(String path, JsonObject select, TypeDetails t) {
private List<Column> checkSelect(JsonObject vd, 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")) {
t = checkForEach(path, select, select.get("forEach"), t);
t = checkForEach(vd, path, select, select.get("forEach"), t);
} else if (select.has("forEachOrNull")) {
t = checkForEachOrNull(path, select, select.get("forEachOrNull"), t);
t = checkForEachOrNull(vd, path, select, select.get("forEachOrNull"), t);
}
if (t != null) {
@ -142,7 +163,7 @@ public class Validator {
if (!(e instanceof JsonObject)) {
error(path+".column["+i+"]", a, "column["+i+"] is a "+e.type().toName()+" not an object", IssueType.INVALID);
} else {
columns.add(checkColumn(path+".column["+i+"]", (JsonObject) e, t));
columns.add(checkColumn(vd, path+".column["+i+"]", (JsonObject) e, t));
}
}
}
@ -158,14 +179,14 @@ public class Validator {
if (!(e instanceof JsonObject)) {
error(path+".select["+i+"]", e, "select["+i+"] is not an object", IssueType.INVALID);
} else {
columns.addAll(checkSelect(path+".select["+i+"]", (JsonObject) e, t));
columns.addAll(checkSelect(vd, path+".select["+i+"]", (JsonObject) e, t));
}
}
}
}
if (select.has("unionAll")) {
columns.addAll(checkUnion(path, select, select.get("unionAll"), t));
columns.addAll(checkUnion(vd, path, select, select.get("unionAll"), t));
}
if (columns.isEmpty()) {
error(path, select, "The select has no columns or selects", IssueType.REQUIRED);
@ -191,7 +212,7 @@ public class Validator {
}
}
private List<Column> checkUnion(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
private List<Column> checkUnion(JsonObject vd, String path, JsonObject focus, JsonElement expression, TypeDetails t) {
JsonElement a = focus.get("unionAll");
if (!(a instanceof JsonArray)) {
error(path+".unionAll", a, "union is not an array", IssueType.INVALID);
@ -203,7 +224,7 @@ public class Validator {
if (!(e instanceof JsonObject)) {
error(path+".unionAll["+i+"]", e, "unionAll["+i+"] is not an object", IssueType.INVALID);
} else {
unionColumns.add(checkSelect(path+".unionAll["+i+"]", (JsonObject) e, t));
unionColumns.add(checkSelect(vd, path+".unionAll["+i+"]", (JsonObject) e, t));
}
i++;
}
@ -242,7 +263,7 @@ public class Validator {
}
}
private Column checkColumn(String path, JsonObject column, TypeDetails t) {
private Column checkColumn(JsonObject vd, String path, JsonObject column, TypeDetails t) {
checkProperties(column, path, "path", "name", "description", "collection", "type", "tag");
if (!column.has("path")) {
@ -260,7 +281,7 @@ public class Validator {
try {
node = fpe.parse(expr);
column.setUserData("path", node);
td = fpe.checkOnTypes(null, resourceName, t, node, warnings);
td = fpe.checkOnTypes(vd, resourceName, t, node, warnings);
} catch (Exception e) {
error(path, expression, e.getMessage(), IssueType.INVALID);
}
@ -296,25 +317,31 @@ public class Validator {
// ok, name is sorted!
if (columnName != null) {
column.setUserData("name", columnName);
boolean isColl = (td.getCollectionStatus() != CollectionStatus.SINGLETON);
boolean isColl = false;
if (column.has("collection")) {
JsonElement collectionJ = column.get("collection");
if (!(collectionJ instanceof JsonBoolean)) {
error(path+".collection", collectionJ, "collection is not a boolean", IssueType.INVALID);
} else {
boolean collection = collectionJ.asJsonBoolean().asBoolean();
if (!collection && isColl) {
isColl = false;
warning(path, column, "collection is false, but the path statement(s) might return multiple values for the column '"+columnName+"' some inputs");
if (collection) {
isColl = true;
}
}
}
if (isColl) {
if (td.getCollectionStatus() == CollectionStatus.SINGLETON) {
hint(path, column, "collection is true, but the path statement(s) can only return single values for the column '"+columnName+"'");
}
} else {
if (arrays == null) {
warning(path, expression, "The column '"+columnName+"' appears to be a collection based on it's path. Collections are not supported in all execution contexts");
} else if (!arrays) {
warning(path, expression, "The column '"+columnName+"' appears to be a collection based on it's path, but this is not allowed in the current execution context");
}
if (td.getCollectionStatus() != CollectionStatus.SINGLETON) {
warning(path, column, "collection is not true, but the path statement(s) might return multiple values for the column '"+columnName+"' for some inputs");
}
}
Set<String> types = new HashSet<>();
if (node.isNullSet()) {
@ -330,7 +357,7 @@ public class Validator {
if (typeJ instanceof JsonString) {
String type = typeJ.asString();
if (!td.hasType(type)) {
error(path+".type", typeJ, "The path expression does not return a value of the type '"+type, IssueType.VALUE);
error(path+".type", typeJ, "The path expression does not return a value of the type '"+type+"' - found "+td.describe(), IssueType.VALUE);
} else {
types.clear();
types.add(simpleType(type));
@ -377,6 +404,8 @@ public class Validator {
case "integer": return ColumnKind.Integer;
case "decimal": return ColumnKind.Decimal;
case "string": return ColumnKind.String;
case "id": return ColumnKind.String;
case "code": return ColumnKind.String;
case "base64Binary": return ColumnKind.Binary;
case "time": return ColumnKind.Time;
default: return ColumnKind.Complex;
@ -384,7 +413,7 @@ public class Validator {
}
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", "id", "code", "date", "time");
}
private String simpleType(String type) {
@ -413,7 +442,7 @@ public class Validator {
return type;
}
private TypeDetails checkForEach(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
private TypeDetails checkForEach(JsonObject vd, String path, JsonObject focus, JsonElement expression, TypeDetails t) {
if (!(expression instanceof JsonString)) {
error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID);
return null;
@ -425,7 +454,7 @@ public class Validator {
try {
ExpressionNode n = fpe.parse(expr);
focus.setUserData("forEach", n);
td = fpe.checkOnTypes(null, resourceName, t, n, warnings);
td = fpe.checkOnTypes(vd, resourceName, t, n, warnings);
} catch (Exception e) {
error(path, expression, e.getMessage(), IssueType.INVALID);
}
@ -438,7 +467,7 @@ public class Validator {
}
}
private TypeDetails checkForEachOrNull(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
private TypeDetails checkForEachOrNull(JsonObject vd, String path, JsonObject focus, JsonElement expression, TypeDetails t) {
if (!(expression instanceof JsonString)) {
error(path+".forEachOrNull", expression, "forEachOrNull is not a string", IssueType.INVALID);
return null;
@ -450,7 +479,7 @@ public class Validator {
try {
ExpressionNode n = fpe.parse(expr);
focus.setUserData("forEachOrNull", n);
td = fpe.checkOnTypes(null, resourceName, t, n, warnings);
td = fpe.checkOnTypes(vd, resourceName, t, n, warnings);
} catch (Exception e) {
error(path, expression, e.getMessage(), IssueType.INVALID);
}
@ -477,69 +506,79 @@ public class Validator {
}
}
if (constant.has("valueBase64Binary")) {
checkIsString(path, constant, "valueBase64Binary");
checkIsString(path, constant, "valueBase64Binary", new Base64BinaryType());
} else if (constant.has("valueBoolean")) {
checkIsBoolean(path, constant, "valueBoolean");
checkIsBoolean(path, constant, "valueBoolean", new BooleanType());
} else if (constant.has("valueCanonical")) {
checkIsString(path, constant, "valueCanonical");
checkIsString(path, constant, "valueCanonical", new CanonicalType());
} else if (constant.has("valueCode")) {
checkIsString(path, constant, "valueCode");
checkIsString(path, constant, "valueCode", new CodeType());
} else if (constant.has("valueDate")) {
checkIsString(path, constant, "valueDate");
checkIsString(path, constant, "valueDate", new DateType());
} else if (constant.has("valueDateTime")) {
checkIsString(path, constant, "valueDateTime");
checkIsString(path, constant, "valueDateTime", new DateTimeType());
} else if (constant.has("valueDecimal")) {
checkIsNumber(path, constant, "valueDecimal");
checkIsNumber(path, constant, "valueDecimal", new DecimalType());
} else if (constant.has("valueId")) {
checkIsString(path, constant, "valueId");
checkIsString(path, constant, "valueId", new IdType());
} else if (constant.has("valueInstant")) {
checkIsString(path, constant, "valueInstant");
checkIsString(path, constant, "valueInstant", new InstantType());
} else if (constant.has("valueInteger")) {
checkIsNumber(path, constant, "valueInteger");
checkIsNumber(path, constant, "valueInteger", new IntegerType());
} else if (constant.has("valueInteger64")) {
checkIsNumber(path, constant, "valueInteger64");
checkIsNumber(path, constant, "valueInteger64", new Integer64Type());
} else if (constant.has("valueOid")) {
checkIsString(path, constant, "valueOid");
checkIsString(path, constant, "valueOid", new OidType());
} else if (constant.has("valueString")) {
checkIsString(path, constant, "valueString");
checkIsString(path, constant, "valueString", new StringType());
} else if (constant.has("valuePositiveInt")) {
checkIsNumber(path, constant, "valuePositiveInt");
checkIsNumber(path, constant, "valuePositiveInt", new PositiveIntType());
} else if (constant.has("valueTime")) {
checkIsString(path, constant, "valueTime");
checkIsString(path, constant, "valueTime", new TimeType());
} else if (constant.has("valueUnsignedInt")) {
checkIsNumber(path, constant, "valueUnsignedInt");
checkIsNumber(path, constant, "valueUnsignedInt", new UnsignedIntType());
} else if (constant.has("valueUri")) {
checkIsString(path, constant, "valueUri");
checkIsString(path, constant, "valueUri", new UriType());
} else if (constant.has("valueUrl")) {
checkIsString(path, constant, "valueUrl");
checkIsString(path, constant, "valueUrl", new UrlType());
} else if (constant.has("valueUuid")) {
checkIsString(path, constant, "valueUuid");
checkIsString(path, constant, "valueUuid", new UuidType());
} else {
error(path, constant, "No value found", IssueType.REQUIRED);
}
}
private void checkIsString(String path, JsonObject constant, String name) {
private void checkIsString(String path, JsonObject constant, String name, PrimitiveType<?> value) {
JsonElement j = constant.get(name);
if (!(j instanceof JsonString)) {
error(path+"."+name, j, name+" must be a string", IssueType.INVALID);
} else {
value.setValueAsString(j.asString());
constant.setUserData("value", value);
}
}
private void checkIsBoolean(String path, JsonObject constant, String name) {
private void checkIsBoolean(String path, JsonObject constant, String name, PrimitiveType<?> value) {
JsonElement j = constant.get(name);
if (!(j instanceof JsonBoolean)) {
error(path+"."+name, j, name+" must be a boolean", IssueType.INVALID);
} else {
value.setValueAsString(j.asString());
constant.setUserData("value", value);
}
}
private void checkIsNumber(String path, JsonObject constant, String name) {
private void checkIsNumber(String path, JsonObject constant, String name, PrimitiveType<?> value) {
JsonElement j = constant.get(name);
if (!(j instanceof JsonNumber)) {
error(path+"."+name, j, name+" must be a number", IssueType.INVALID);
} else {
value.setValueAsString(j.asString());
constant.setUserData("value", value);
}
}
private void checkWhere(String path, JsonObject where) {
private void checkWhere(JsonObject vd, String path, JsonObject where) {
checkProperties(where, path, "path", "description");
String expr = where.asString("path");
@ -553,7 +592,7 @@ public class Validator {
try {
ExpressionNode n = fpe.parse(expr);
where.setUserData("path", n);
td = fpe.checkOnTypes(null, resourceName, types, n, warnings);
td = fpe.checkOnTypes(vd, resourceName, types, n, warnings);
} catch (Exception e) {
error(path, where.get("path"), e.getMessage(), IssueType.INVALID);
}

View File

@ -83,23 +83,21 @@ public class SQLOnFhirTests {
this.resources = resources;
this.testCase = testCase;
}
}
public static Stream<Arguments> data() throws ParserConfigurationException, SAXException, IOException {
List<Arguments> objects = new ArrayList<>();
File dir = ManagedFileAccess.file("/Users/grahamegrieve/work/sql-on-fhir-v2/tests/content");
for (File f : dir.listFiles()) {
if (f.getName().endsWith(".json")) {
JsonObject json = JsonParser.parseObject(f);
String name1 = f.getName().replace(".json", "");
List<JsonObject> resources = json.getJsonObjects("resources");
int i = 0;
for (JsonObject test : json.getJsonObjects("tests")) {
String name2 = test.asString("title");
objects.add(Arguments.of(name1+":"+name2, new TestDetails(name1+":"+name2, "$.tests["+i+"]", resources, test)));
i++;
}
JsonArray testFiles = (JsonArray) JsonParser.parse(TestingUtilities.loadTestResourceStream("sql-on-fhir", "manifest.json"));
for (String s : testFiles.asStrings()) {
JsonObject json = JsonParser.parseObject(TestingUtilities.loadTestResourceStream("sql-on-fhir", s));
String name1 = s.replace(".json", "");
List<JsonObject> resources = json.getJsonObjects("resources");
int i = 0;
for (JsonObject test : json.getJsonObjects("tests")) {
String name2 = test.asString("title");
objects.add(Arguments.of(name1+":"+name2, new TestDetails(name1+":"+name2, "$.tests["+i+"]", resources, test)));
i++;
}
}
return objects.stream();
@ -110,7 +108,6 @@ public class SQLOnFhirTests {
@SuppressWarnings("deprecation")
@ParameterizedTest(name = "{index}: file {0}")
@MethodSource("data")
@Disabled
public void test(String name, TestDetails test) throws FileNotFoundException, IOException, FHIRException, org.hl7.fhir.exceptions.FHIRException, UcumException {
this.details = test;
Runner runner = new Runner();
@ -137,8 +134,8 @@ public class SQLOnFhirTests {
rows.add("rows", results);
JsonObject exp = new JsonObject();
exp.add("rows", test.testCase.getJsonArray("expect"));
sortResults(exp);
sortResults(rows);
// sortResults(exp);
// sortResults(rows);
String expS = JsonParser.compose(exp, true);
String rowS = JsonParser.compose(rows, true);
String c = CompareUtilities.checkJsonSrcIsSame(name, expS, rowS, null);

View File

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

View File

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

View File

@ -1491,7 +1491,7 @@ public class Utilities {
}
public static boolean startsWithInList(String s, Collection<String> list) {
if (s == null) {
if (s == null || list == null) {
return false;
}
for (String l : list) {
@ -1819,9 +1819,14 @@ public class Utilities {
private static Object applyDatePrecision(String v, int precision) {
switch (precision) {
case 4: return v.substring(0, 4);
case 6: return v.substring(0, 7);
case 8: return v.substring(0, 10);
case 4:
return v.substring(0, 4);
case 6:
case 7:
return v.substring(0, 7);
case 8:
case 10:
return v.substring(0, 10);
case 14: return v.substring(0, 17);
case 17: return v;
}
@ -2304,4 +2309,16 @@ public class Utilities {
}
}
public static boolean listValueStartsWith(String s, Set<String> list) {
if (s == null || list == null) {
return false;
}
for (String l : list) {
if (l.startsWith(s)) {
return true;
}
}
return false;
}
}

View File

@ -210,26 +210,30 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
Utilities.createDirectory(cacheFolder.getAbsolutePath());
createIniFile();
} else {
if (!isCacheFolderValid()) {
if (!iniFileExists()) {
createIniFile();
}
if (!isIniFileCurrentVersion()) {
clearCache();
createIniFile();
} else {
deleteOldTempDirectories();
}
deleteOldTempDirectories();
}
return null;
});
}
private boolean isCacheFolderValid() throws IOException {
private boolean iniFileExists() throws IOException {
String iniPath = getPackagesIniPath();
File iniFile = ManagedFileAccess.file(iniPath);
if (!(iniFile.exists())) {
return false;
}
return iniFile.exists();
}
private boolean isIniFileCurrentVersion() throws IOException {
String iniPath = getPackagesIniPath();
IniFile ini = new IniFile(iniPath);
String v = ini.getStringProperty("cache", "version");
return CACHE_VERSION.equals(v);
String version = ini.getStringProperty("cache", "version");
return CACHE_VERSION.equals(version);
}
private void deleteOldTempDirectories() throws IOException {

View File

@ -237,9 +237,11 @@ public class NpmPackage {
public List<String> listFiles() {
List<String> res = new ArrayList<>();
if (folder != null) {
for (File f : folder.listFiles()) {
if (!f.isDirectory() && !Utilities.existsInList(f.getName(), "package.json", ".index.json", ".index.db", ".oids.json", ".oids.db")) {
res.add(f.getName());
if (folder.exists()) {
for (File f : folder.listFiles()) {
if (!f.isDirectory() && !Utilities.existsInList(f.getName(), "package.json", ".index.json", ".index.db", ".oids.json", ".oids.db")) {
res.add(f.getName());
}
}
}
} else {
@ -648,7 +650,17 @@ public class NpmPackage {
}
/**
* Create a package .index.json file for a package folder.
* <p>
* See <a href="https://hl7.org/fhir/packages.html#2.1.10.4">the FHIR specification</a> for details on .index.json
* format and usage.
*
* @param desc
* @param folder
* @throws FileNotFoundException
* @throws IOException
*/
public void indexFolder(String desc, NpmPackageFolder folder) throws FileNotFoundException, IOException {
List<String> remove = new ArrayList<>();
NpmPackageIndexBuilder indexer = new NpmPackageIndexBuilder();

View File

@ -31,18 +31,19 @@ public class PackageHacker {
public static void main(String[] args) throws FileNotFoundException, IOException {
// new PackageHacker().massEdit(new File("/Users/grahamegrieve/web/hl7.org/fhir"));
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot1/hl7.fhir.r6.core.tgz");
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot1/hl7.fhir.r6.corexml.tgz");
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot1/hl7.fhir.r6.examples.tgz");
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot1/hl7.fhir.r6.expansions.tgz");
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot1/hl7.fhir.r6.search.tgz");
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot2/hl7.fhir.r6.core.tgz");
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot2/hl7.fhir.r6.corexml.tgz");
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot2/hl7.fhir.r6.examples.tgz");
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot2/hl7.fhir.r6.expansions.tgz");
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot2/hl7.fhir.r6.search.tgz");
// new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot1/hl7.fhir.r6.core.tgz");
// new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot1/hl7.fhir.r6.corexml.tgz");
// new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot1/hl7.fhir.r6.examples.tgz");
// new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot1/hl7.fhir.r6.expansions.tgz");
// new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot1/hl7.fhir.r6.search.tgz");
// new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot2/hl7.fhir.r6.core.tgz");
// new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot2/hl7.fhir.r6.corexml.tgz");
// new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot2/hl7.fhir.r6.examples.tgz");
// new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot2/hl7.fhir.r6.expansions.tgz");
// new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/6.0.0-ballot2/hl7.fhir.r6.search.tgz");
// new PackageHacker().edit(args[0]);
// new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/us/core/v311/package.tgz", "http://hl7.org/fhir/us/core/STU3.1.1");
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/us/core/v700/package.tgz", "http://hl7.org/fhir/us/core/STU7");
}
// private void massEdit(File dir) throws IOException {
@ -91,7 +92,7 @@ public class PackageHacker {
// }
// }
private void edit(String name) throws FileNotFoundException, IOException {
private void edit(String name, String path) throws FileNotFoundException, IOException {
File f = ManagedFileAccess.file(name);
if (!f.exists())
throw new Error("Unable to find "+f.getAbsolutePath());
@ -107,15 +108,15 @@ public class PackageHacker {
System.out.println("Altering Package "+f.getAbsolutePath());
System.out.println(nice(pck.getNpm()));
if (change(pck.getNpm())) {
if (change(pck.getNpm(), path)) {
System.out.println("Revised Package");
System.out.println("=======================");
System.out.println(nice(pck.getNpm()));
System.out.println("=======================");
// System.out.print("save? y/n: ");
// int r = System.in.read();
// if (r == 'y') {
System.out.print("save? y/n: ");
int r = System.in.read();
if (r == 'y') {
f.renameTo(ManagedFileAccess.file(Utilities.changeFileExt(name, ".tgz.bak")));
FileOutputStream fso = ManagedFileAccess.outStream(f);
try {
@ -123,7 +124,7 @@ public class PackageHacker {
} finally {
fso.close();
}
// }
}
}
}
@ -142,13 +143,15 @@ public class PackageHacker {
return JsonParser.compose(json, true);
}
private boolean change(JsonObject npm) throws FileNotFoundException, IOException {
// fixVersions(npm, ver);
if (npm.has("notForPublication")) {
npm.remove("notForPublication");
return true;
}
return false;
private boolean change(JsonObject npm, String path) throws FileNotFoundException, IOException {
npm.remove("url");
npm.add("url", path);
return true;
// if (npm.has("notForPublication")) {
// npm.remove("notForPublication");
// return true;
// }
// return false;
}
private void fixVersionInContent(Map<String, byte[]> content) {

View File

@ -1,22 +1,25 @@
package org.hl7.fhir.utilities.npm;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import org.hl7.fhir.utilities.IniFile;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
@ -100,7 +103,6 @@ public class FilesystemPackageManagerTests {
@DisabledOnOs(OS.WINDOWS)
public void testSystemCacheDirectory() throws IOException {
File folder = new FilesystemPackageCacheManager.Builder().withSystemCacheFolder().getCacheFolder();
assertEquals( "/var/lib/.fhir/packages", folder.getAbsolutePath());
}
@ -124,6 +126,118 @@ public class FilesystemPackageManagerTests {
return params.stream();
}
private void createDummyTemp(File cacheDirectory, String lowerCase) throws IOException {
createDummyPackage(cacheDirectory, lowerCase);
}
private void createDummyPackage(File cacheDirectory, String packageName, String packageVersion) throws IOException {
String directoryName = packageName + "#" + packageVersion;
createDummyPackage(cacheDirectory, directoryName);
}
private static void createDummyPackage(File cacheDirectory, String directoryName) throws IOException {
File packageDirectory = ManagedFileAccess.file(cacheDirectory.getAbsolutePath(), directoryName);
packageDirectory.mkdirs();
File dummyContentFile = ManagedFileAccess.file(packageDirectory.getAbsolutePath(), "dummy.txt");
FileWriter wr = new FileWriter(dummyContentFile);
wr.write("Ain't nobody here but us chickens");
wr.flush();
wr.close();
}
private void assertThatDummyTempExists(File cacheDirectory, String dummyTempPackage) throws IOException {
File dummyTempDirectory = ManagedFileAccess.file(cacheDirectory.getAbsolutePath(), dummyTempPackage);
assertThat(dummyTempDirectory).exists();
File dummyContentFile = ManagedFileAccess.file(dummyTempDirectory.getAbsolutePath(), "dummy.txt");
assertThat(dummyContentFile).exists();
}
@Test
public void testCreatesIniIfDoesntExistAndCacheStaysIntact() throws IOException {
File cacheDirectory = ManagedFileAccess.fromPath(Files.createTempDirectory("fpcm-multithreadingTest"));
File cacheIni = ManagedFileAccess.file(cacheDirectory.getAbsolutePath(), "packages.ini");
createDummyPackage(cacheDirectory, "example.fhir.uv.myig", "1.2.3");
String dummyTempPackage = UUID.randomUUID().toString().toLowerCase();
createDummyTemp(cacheDirectory, dummyTempPackage);
assertThatDummyTempExists(cacheDirectory, dummyTempPackage);
assertThat(cacheIni).doesNotExist();
FilesystemPackageCacheManager filesystemPackageCacheManager = new FilesystemPackageCacheManager.Builder().withCacheFolder(cacheDirectory.getAbsolutePath()).build();
assertInitializedTestCacheIsValid(cacheDirectory, true);
}
@Test
public void testClearsCacheIfVersionIsWrong() throws IOException {
File cacheDirectory = ManagedFileAccess.fromPath(Files.createTempDirectory("fpcm-multithreadingTest"));
File cacheIni = ManagedFileAccess.file(cacheDirectory.getAbsolutePath(), "packages.ini");
createDummyPackage(cacheDirectory, "example.fhir.uv.myig", "1.2.3");
String dummyTempPackage = UUID.randomUUID().toString().toLowerCase();
createDummyTemp(cacheDirectory, dummyTempPackage);
assertThatDummyTempExists(cacheDirectory, dummyTempPackage);
IniFile ini = new IniFile(cacheIni.getAbsolutePath());
ini.setStringProperty("cache", "version", "2", null);
ini.save();
assertThat(cacheIni).exists();
FilesystemPackageCacheManager filesystemPackageCacheManager = new FilesystemPackageCacheManager.Builder().withCacheFolder(cacheDirectory.getAbsolutePath()).build();
assertInitializedTestCacheIsValid(cacheDirectory, false);
}
@Test
public void testCacheStaysIntactIfVersionIsTheSame() throws IOException {
File cacheDirectory = ManagedFileAccess.fromPath(Files.createTempDirectory("fpcm-multithreadingTest"));
File cacheIni = ManagedFileAccess.file(cacheDirectory.getAbsolutePath(), "packages.ini");
createDummyPackage(cacheDirectory, "example.fhir.uv.myig", "1.2.3");
String dummyTempPackage = UUID.randomUUID().toString().toLowerCase();
createDummyTemp(cacheDirectory, dummyTempPackage);
assertThatDummyTempExists(cacheDirectory, dummyTempPackage);
IniFile ini = new IniFile(cacheIni.getAbsolutePath());
ini.setStringProperty("cache", "version", "3", null);
ini.save();
assertThat(cacheIni).exists();
FilesystemPackageCacheManager filesystemPackageCacheManager = new FilesystemPackageCacheManager.Builder().withCacheFolder(cacheDirectory.getAbsolutePath()).build();
assertInitializedTestCacheIsValid(cacheDirectory, true);
}
private void assertInitializedTestCacheIsValid(File cacheDirectory, boolean dummyPackageShouldExist) throws IOException {
assertThat(cacheDirectory).exists();
File iniFile = ManagedFileAccess.file(cacheDirectory.getAbsolutePath(), "packages.ini");
assertThat(ManagedFileAccess.file(cacheDirectory.getAbsolutePath(), "packages.ini")).exists();
IniFile ini = new IniFile(iniFile.getAbsolutePath());
String version = ini.getStringProperty("cache", "version");
assertThat(version).isEqualTo("3");
File[] files = cacheDirectory.listFiles();
if (dummyPackageShouldExist) {
// Check that only packages.ini and our dummy package are in the cache. Our previous temp should be deleted.
assertThat(files).hasSize(2); // packages.ini and example.fhir.uv.myig#1.2.3 (directory)
File dummyPackage = ManagedFileAccess.file(cacheDirectory.getAbsolutePath(), "example.fhir.uv.myig#1.2.3");
assertThat(dummyPackage).exists();
File dummyContentFile = ManagedFileAccess.file(dummyPackage.getAbsolutePath(), "dummy.txt");
assertThat(dummyContentFile).exists();
} else {
// Check that only packages.ini is in the cache.
assertThat(files).hasSize(1);
}
}
@MethodSource("packageCacheMultiThreadTestParams")
@ParameterizedTest
public void packageCacheMultiThreadTest(final int threadTotal, final int packageCacheManagerTotal) throws IOException {

View File

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

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.24-SNAPSHOT</version>
<version>6.3.26-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -119,37 +119,6 @@
<optional>true</optional>
</dependency>
<!-- CQL-to-ELM -->
<dependency>
<groupId>info.cqframework</groupId>
<artifactId>cql</artifactId>
<version>${info_cqframework_version}</version>
</dependency>
<dependency>
<groupId>info.cqframework</groupId>
<artifactId>model</artifactId>
<version>${info_cqframework_version}</version>
</dependency>
<dependency>
<groupId>info.cqframework</groupId>
<artifactId>elm</artifactId>
<version>${info_cqframework_version}</version>
</dependency>
<dependency>
<groupId>info.cqframework</groupId>
<artifactId>cql-to-elm</artifactId>
<version>${info_cqframework_version}</version>
</dependency>
<dependency>
<groupId>info.cqframework</groupId>
<artifactId>quick</artifactId>
<version>${info_cqframework_version}</version>
</dependency>
<dependency>
<groupId>info.cqframework</groupId>
<artifactId>qdm</artifactId>
<version>${info_cqframework_version}</version>
</dependency>
<!-- OkHttpDependency -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>

View File

@ -226,6 +226,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
@Getter @Setter private boolean allowDoubleQuotesInFHIRPath;
@Getter @Setter private boolean checkIPSCodes;
@Getter @Setter private BestPracticeWarningLevel bestPracticeLevel;
@Getter @Setter private boolean unknownCodeSystemsCauseErrors;
@Getter @Setter private Locale locale;
@Getter @Setter private List<ImplementationGuide> igs = new ArrayList<>();
@Getter @Setter private List<String> extensionDomains = new ArrayList<>();
@ -289,6 +290,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
fhirPathEngine = other.fhirPathEngine;
igLoader = other.igLoader;
jurisdiction = other.jurisdiction;
unknownCodeSystemsCauseErrors = other.unknownCodeSystemsCauseErrors;
}
/**
@ -906,6 +908,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
if (policyAdvisor != null) {
validator.setPolicyAdvisor(policyAdvisor);
}
validator.setUnknownCodeSystemsCauseErrors(unknownCodeSystemsCauseErrors);
return validator;
}

View File

@ -163,6 +163,9 @@ public class CliContext {
@JsonProperty("bestPracticeLevel")
private BestPracticeWarningLevel bestPracticeLevel = BestPracticeWarningLevel.Warning;
@JsonProperty("unknownCodeSystemsCauseErrors")
private boolean unknownCodeSystemsCauseErrors;
@JsonProperty("baseEngine")
public String getBaseEngine() {
return baseEngine;
@ -832,6 +835,7 @@ public class CliContext {
Objects.equals(watchMode, that.watchMode) &&
Objects.equals(bestPracticeLevel, that.bestPracticeLevel) &&
Objects.equals(watchScanDelay, that.watchScanDelay) &&
Objects.equals(unknownCodeSystemsCauseErrors, that.unknownCodeSystemsCauseErrors) &&
Objects.equals(watchSettleTime, that.watchSettleTime) ;
}
@ -839,8 +843,8 @@ public class CliContext {
public int hashCode() {
return Objects.hash(baseEngine, doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching,
noExtensibleBindingMessages, noInvariants, displayWarnings, wantInvariantsInMessages, map, output, outputSuffix, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, srcLang, tgtLang, fhirpath, snomedCT,
targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars, watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel,
htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes);
targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars,
watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel, unknownCodeSystemsCauseErrors, htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes);
}
@Override
@ -899,6 +903,7 @@ public class CliContext {
", bestPracticeLevel=" + bestPracticeLevel +
", watchSettleTime=" + watchSettleTime +
", watchScanDelay=" + watchScanDelay +
", unknownCodeSystemsCauseErrors=" + unknownCodeSystemsCauseErrors +
'}';
}
@ -956,4 +961,17 @@ public class CliContext {
return this;
}
@JsonProperty("unknownCodeSystemsCauseErrors")
public boolean isUnknownCodeSystemsCauseErrors() {
return unknownCodeSystemsCauseErrors;
}
@JsonProperty("unknownCodeSystemsCauseErrors")
public void setUnknownCodeSystemsCauseErrors(boolean unknownCodeSystemsCauseErrors) {
this.unknownCodeSystemsCauseErrors = unknownCodeSystemsCauseErrors;
}
}

View File

@ -581,6 +581,7 @@ public class ValidationService {
}
validationEngine.getBundleValidationRules().addAll(cliContext.getBundleValidationRules());
validationEngine.setJurisdiction(CodeSystemUtilities.readCoding(cliContext.getJurisdiction()));
validationEngine.setUnknownCodeSystemsCauseErrors(cliContext.isUnknownCodeSystemsCauseErrors());
TerminologyCache.setNoCaching(cliContext.isNoInternalCaching());
validationEngine.prepare(); // generate any missing snapshots
System.out.println(" go (" + timeTracker.milestone() + ")");

View File

@ -94,8 +94,7 @@ public class Params {
public static final String DISABLE_DEFAULT_RESOURCE_FETCHER = "-disable-default-resource-fetcher";
public static final String CHECK_IPS_CODES = "-check-ips-codes";
public static final String BEST_PRACTICE = "-best-practice";
public static final String UNKNOWN_CODESYSTEMS_CAUSE_ERROR = "-unknown-codesystems-cause-errors";
public static final String RUN_TESTS = "-run-tests";
@ -320,6 +319,8 @@ public class Params {
cliContext.setCrumbTrails(true);
} else if (args[i].equals(FOR_PUBLICATION)) {
cliContext.setForPublication(true);
} else if (args[i].equals(UNKNOWN_CODESYSTEMS_CAUSE_ERROR)) {
cliContext.setUnknownCodeSystemsCauseErrors(true);
} else if (args[i].equals(VERBOSE)) {
cliContext.setCrumbTrails(true);
} else if (args[i].equals(ALLOW_EXAMPLE_URLS)) {

View File

@ -46,7 +46,7 @@ import javax.annotation.Nonnull;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils;
import org.fhir.ucum.Decimal;
import org.hl7.elm.r1.Code;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
@ -598,6 +598,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean example ;
private IDigitalSignatureServices signatureServices;
private ContextUtilities cu;
private boolean unknownCodeSystemsCauseErrors;
public InstanceValidator(@Nonnull IWorkerContext theContext, @Nonnull IEvaluationContext hostServices, @Nonnull XVerExtensionManager xverManager) {
super(theContext, xverManager, false);
@ -1125,7 +1126,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
timeTracker.tx(t, "vc "+system+"#"+code+" '"+display+"'");
if (s == null)
return true;
ok = processTxIssues(errors, s, element, path, null, "no binding on code", false, null) & ok;
ok = processTxIssues(errors, s, element, path, false, null, null) & ok;
if (s.isOk()) {
if (s.getMessage() != null && !s.messageIsInIssues()) {
@ -1381,7 +1382,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (cc.hasCoding()) {
long t = System.nanoTime();
ValidationResult vr = checkCodeOnServer(stack, null, cc);
bh.see(processTxIssues(errors, vr, element, path, org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, null, false, null));
bh.see(processTxIssues(errors, vr, element, path, false, null, null));
timeTracker.tx(t, "vc " + cc.toString());
}
} catch (CheckCodeOnServerException e) {
@ -1465,7 +1466,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else {
checked.set(true);
ValidationResult vr = checkCodeOnServer(stack, valueset, cc);
bh.see(processTxIssues(errors, vr, element, path, notFoundSeverityForBinding(strength), notFoundSeverityNoteForBinding(strength), false, vsRef));
bh.see(processTxIssues(errors, vr, element, path, false, vsRef, strength));
if (!vr.isOk()) {
bindingsOk = false;
if (vr.getErrorClass() != null && vr.getErrorClass() == TerminologyServiceErrorClass.NOSERVICE) {
@ -1533,33 +1534,34 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return checkDisp;
}
private String notFoundSeverityNoteForBinding(BindingStrength strength) {
if (strength == BindingStrength.REQUIRED) {
return "error because this is a required binding";
} else {
return null;
}
}
// private String notFoundSeverityNoteForBinding(BindingStrength strength, Set<String> systems) {
// if (strength == BindingStrength.REQUIRED &&
// (Utilities.listValueStartsWith("http://hl7.org/fhir", systems) || Utilities.listValueStartsWith("http://terminology.hl7.org", systems))) {
// return "error because this is a required binding to an HL7 code system";
// } else {
// return null;
// }
// }
//
// /**
// * The terminology server will report an error for an unknown code system or version, or a dependent valueset
// *
// * but we only care for validation if the binding strength is strong enough.
// * @param binding
// * @return
// */
// private org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity notFoundSeverityForBinding(BindingStrength strength, String systems) {
// if (strength == BindingStrength.REQUIRED &&
// (Utilities.listValueStartsWith("http://hl7.org/fhir", systems) || Utilities.listValueStartsWith("http://terminology.hl7.org", systems))) {
// return org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR;
// } else if (strength == BindingStrength.REQUIRED || strength == BindingStrength.EXTENSIBLE) {
// return org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING;
// } else {
// return org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION;
// }
// }
/**
* The terminology server will report an error for an unknown code system or version, or a dependent valueset
*
* but we only care for validation if the binding strength is strong enough.
* @param binding
* @return
*/
private org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity notFoundSeverityForBinding(BindingStrength strength) {
if (strength == BindingStrength.REQUIRED) {
return org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR;
} else if (strength == BindingStrength.EXTENSIBLE) {
return org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING;
} else {
return org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION;
}
}
private boolean processTxIssues(List<ValidationMessage> errors, ValidationResult vr, Element element, String path,
org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity notFoundLevel, String notFoundNote, boolean ignoreCantInfer, String vsurl) {
private boolean processTxIssues(List<ValidationMessage> errors, ValidationResult vr, Element element, String path, boolean ignoreCantInfer, String vsurl, BindingStrength bs) {
boolean ok = true;
if (vr != null) {
for (OperationOutcomeIssueComponent iss : vr.getIssues()) {
@ -1568,11 +1570,34 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
&& !(ignoreCantInfer || iss.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "cannot-infer"))) {
OperationOutcomeIssueComponent i = iss.copy();
if (i.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "not-found")) {
if (notFoundLevel != null && i.getSeverity().isHigherThan(notFoundLevel) || (vsurl != null && i.getDetails().getText().contains(vsurl))) {
i.setSeverity(notFoundLevel);
String msg = iss.getDetails().getText();
boolean isHL7 = msg == null ? false : msg.contains("http://hl7.org/fhir") || msg.contains("http://terminology.hl7.org");
org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity notFoundLevel = null;
String notFoundNote = null;
if (bs == null) {
notFoundLevel = org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING;
notFoundNote = null; // "binding=null";
} else if (bs == BindingStrength.REQUIRED && isHL7) {
notFoundLevel = org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR;
notFoundNote = "error because this is a required binding to an HL7 code system";
} else if (bs == BindingStrength.REQUIRED && unknownCodeSystemsCauseErrors) {
notFoundLevel = org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR;
notFoundNote = "error because this is a required binding";
} else if (bs == BindingStrength.REQUIRED) {
notFoundLevel = org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING;
notFoundNote = null; // "binding=required";
} else if (bs == BindingStrength.EXTENSIBLE) {
notFoundLevel = org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING;
notFoundNote = null; // "binding=extensible";
} else {
notFoundLevel = org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING;
notFoundNote = null; // "binding="+bs.toCode();
}
if (notFoundNote != null) {
i.getDetails().setText(i.getDetails().getText()+" ("+notFoundNote+")");
if (notFoundLevel != null && i.getSeverity().isHigherThan(notFoundLevel)) { // && (vsurl != null && i.getDetails().getText().contains(vsurl))) {
i.setSeverity(notFoundLevel);
if (notFoundNote != null) {
i.getDetails().setText(i.getDetails().getText()+" ("+notFoundNote+")");
}
}
}
if (baseOptions.isDisplayWarningMode() && i.getSeverity() == org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR && i.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "invalid-display")) {
@ -1592,7 +1617,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
boolean ok = true;
if (isNotBlank(nextCoding.getCode()) && isNotBlank(nextCoding.getSystem()) && context.supportsSystem(nextCoding.getSystem(), baseOptions.getFhirVersion())) {
ValidationResult vr = checkCodeOnServer(stack, valueset, nextCoding);
ok = processTxIssues(errors, vr, element, path, null, "ex-checkBindings", false, null) && ok;
ok = processTxIssues(errors, vr, element, path, false, null, null) && ok;
if (vr.getSeverity() != null && !vr.messageIsInIssues()) {
if (vr.getSeverity() == IssueSeverity.INFORMATION) {
@ -1721,7 +1746,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (strength == BindingStrength.REQUIRED) {
removeTrackedMessagesForLocation(errors, element, path);
}
ok = processTxIssues(errors, vr, element, path, notFoundSeverityForBinding(strength), notFoundSeverityNoteForBinding(strength), false, vsRef) && ok;
ok = processTxIssues(errors, vr, element, path, false, vsRef, strength) && ok;
timeTracker.tx(t, "vc "+system+"#"+code+" '"+display+"'");
if (vr != null && !vr.isOk()) {
if (vr.IsNoService())
@ -1766,6 +1791,21 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return ok;
}
private Set<String> getUnknownSystems(ValidationResult vr) {
if (vr == null) {
return null;
}
if (vr.getUnknownSystems() != null && !vr.getUnknownSystems().isEmpty()) {
return vr.getUnknownSystems();
}
if (vr.getSystem() != null) {
Set<String> set = new HashSet<String>();
set.add(vr.getSystem());
return set;
}
return null;
}
private boolean convertCDACodeToCodeableConcept(List<ValidationMessage> errors, String path, Element element, StructureDefinition logical, CodeableConcept cc) {
boolean ok = true;
cc.setText(element.getNamedChildValue("originalText", false));
@ -1851,7 +1891,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
try {
long t = System.nanoTime();
ValidationResult vr = checkCodeOnServer(stack, valueset, cc);
ok = processTxIssues(errors, vr, element, path, null, "ex-checkMaxValueSet", false, maxVSUrl) && ok;
ok = processTxIssues(errors, vr, element, path, false, maxVSUrl, null) && ok;
timeTracker.tx(t, "vc "+cc.toString());
if (!vr.isOk()) {
if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
@ -1890,7 +1930,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
try {
long t = System.nanoTime();
ValidationResult vr = checkCodeOnServer(stack, valueset, c);
ok = processTxIssues(errors, vr, element, path, null, "ex-checkMaxValueSet-2", false, maxVSUrl) && ok;
ok = processTxIssues(errors, vr, element, path, false, maxVSUrl, null) && ok;
timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'");
if (!vr.isOk()) {
@ -1921,7 +1961,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
try {
long t = System.nanoTime();
ValidationResult vr = checkCodeOnServer(stack, valueset, value, baseOptions);
ok = processTxIssues(errors, vr, element, path, null, "ex-checkMaxValueSet-3", false, maxVSUrl) && ok;
ok = processTxIssues(errors, vr, element, path, false, maxVSUrl, null) && ok;
timeTracker.tx(t, "vc "+value);
if (!vr.isOk()) {
if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
@ -2045,7 +2085,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
checked.set(true);
vr = checkCodeOnServer(stack, valueset, c);
}
ok = processTxIssues(errors, vr, element, path, notFoundSeverityForBinding(strength), notFoundSeverityNoteForBinding(strength), strength == BindingStrength.EXTENSIBLE, vsRef) && ok;
ok = processTxIssues(errors, vr, element, path, strength == BindingStrength.EXTENSIBLE, vsRef, strength) && ok;
timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'");
if (strength == BindingStrength.REQUIRED) {
@ -3510,7 +3550,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
vr = checkCodeOnServer(stack, vs, value, options);
}
ok = processTxIssues(errors, vr, element, path, notFoundSeverityForBinding(binding.getStrength()), notFoundSeverityNoteForBinding(binding.getStrength()), binding.getStrength() != BindingStrength.REQUIRED, binding.getValueSet()) && ok;
ok = processTxIssues(errors, vr, element, path, binding.getStrength() != BindingStrength.REQUIRED, binding.getValueSet(), binding.getStrength()) && ok;
timeTracker.tx(t, "vc "+value+"");
if (binding.getStrength() == BindingStrength.REQUIRED) {
@ -7791,4 +7831,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
this.fetcher = value;
return this;
}
public boolean isUnknownCodeSystemsCauseErrors() {
return unknownCodeSystemsCauseErrors;
}
public void setUnknownCodeSystemsCauseErrors(boolean unknownCodeSystemsCauseErrors) {
this.unknownCodeSystemsCauseErrors = unknownCodeSystemsCauseErrors;
}
}

View File

@ -61,7 +61,7 @@ public class FHIRPathExpressionFixer {
}
// con-3 in R4
if (expr.equals("clinicalStatus.exists() or verificationStatus.coding.where(system='http://terminology.hl7.org/CodeSystem/condition-ver-status' and code = 'entered-in-error').exists() or category.select($this='problem-list-item').empty()")) {
return "clinicalStatus.exists() or verificationStatus.coding.where(system='http://terminology.hl7.org/CodeSystem/condition-ver-status' and code = 'entered-in-error').exists() or category.coding.exists(system='http://terminology.hl7.org/CodeSystem/condition-category' and code ='problem-list-item').empty()";
return "(verificationStatus.coding.where(system='http://terminology.hl7.org/CodeSystem/condition-ver-status' and code = 'entered-in-error').exists() and category.coding.exists(system='http://terminology.hl7.org/CodeSystem/condition-category' and code ='problem-list-item').empty()) implies (clinicalStatus.exists())";
}
// R5 ballot

View File

@ -8,10 +8,10 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.hl7.fhir.ParametersParameter;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;

View File

@ -247,9 +247,9 @@ public class ValidationEngineTests {
Assertions.assertTrue(checkOutcomes("testObs102", op,
"Observation.text.div null error/invalid: Wrong namespace on the XHTML ('null', should be 'http://www.w3.org/1999/xhtml')\n"+
"Observation.category null information/business-rule: Reference to experimental CodeSystem http://hl7.org/fhir/observation-category\n"+
"Observation.code.coding[2].system null information/not-found: A definition for CodeSystem 'http://acme.org/devices/clinical-codes' could not be found, so the code cannot be validated\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have a performer\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have an effective[x] ()"));
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have an effective[x] ()\n"+
"Observation.code.coding[2].system null warning/not-found: A definition for CodeSystem 'http://acme.org/devices/clinical-codes' could not be found, so the code cannot be validated"));
verifyNoTerminologyRequests(logger);
}
@ -265,8 +265,8 @@ public class ValidationEngineTests {
System.out.println(" .. load USCore");
OperationOutcome op = ve.validate(FhirFormat.XML, TestingUtilities.loadTestResourceStream("validator", "observation301.xml"), null);
Assertions.assertTrue(checkOutcomes("test301", op,
"Observation.code.coding[3].system null information/not-found: A definition for CodeSystem 'http://acme.org/devices/clinical-codes' could not be found, so the code cannot be validated\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have a performer"));
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have a performer\n"+
"Observation.code.coding[3].system null warning/not-found: A definition for CodeSystem 'http://acme.org/devices/clinical-codes' could not be found, so the code cannot be validated"));
verifyNoTerminologyRequests(logger);
}

View File

@ -14,14 +14,14 @@
HAPI FHIR
-->
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.24-SNAPSHOT</version>
<version>6.3.26-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<commons_compress_version>1.26.0</commons_compress_version>
<guava_version>32.0.1-jre</guava_version>
<hapi_fhir_version>6.4.1</hapi_fhir_version>
<validator_test_case_version>1.5.21-SNAPSHOT</validator_test_case_version>
<validator_test_case_version>1.5.22-SNAPSHOT</validator_test_case_version>
<jackson_version>2.17.0</jackson_version>
<junit_jupiter_version>5.9.2</junit_jupiter_version>
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>