Add support for fetching unknown value sets from tx.fhir.org/ecosystem

This commit is contained in:
Grahame Grieve 2024-01-16 11:45:13 +11:00
parent ec67f6c97a
commit de0024ceec
29 changed files with 524 additions and 85 deletions

View File

@ -10,4 +10,6 @@ public class BindingResolution {
}
public String display;
public String url;
public String uri;
public boolean external;
}

View File

@ -2722,8 +2722,8 @@ public class ProfileUtilities extends TranslatingUtilities {
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "illegal attempt to change the binding on "+derived.getPath()+" from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode(), ValidationMessage.IssueSeverity.ERROR));
// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) {
ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet(), srcSD);
ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet(), derivedSrc);
ValueSet baseVs = context.findTxResource(ValueSet.class, base.getBinding().getValueSet(), srcSD);
ValueSet contextVs = context.findTxResource(ValueSet.class, derived.getBinding().getValueSet(), derivedSrc);
if (baseVs == null) {
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
} else if (contextVs == null) {

View File

@ -124,6 +124,7 @@ import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.Termi
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.CacheToken;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet;
import org.hl7.fhir.r5.terminologies.validation.VSCheckerException;
import org.hl7.fhir.r5.terminologies.validation.ValueSetValidator;
import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
@ -851,7 +852,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
Set<String> systems = findRelevantSystems(vs);
TerminologyClientContext tc = terminologyClientManager.chooseServer(systems, true);
if (tc == null) {
res = new ValueSetExpansionOutcome("No server available", TerminologyServiceErrorClass.INTERNAL_ERROR, true);
return new ValueSetExpansionOutcome("No server available", TerminologyServiceErrorClass.INTERNAL_ERROR, true);
}
Parameters p = constructParameters(tc, vs, hierarchical);
for (ConceptSetComponent incl : vs.getCompose().getInclude()) {
@ -3173,4 +3174,58 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
txCache.clear();
}
private <T extends Resource> T doFindTxResource(Class<T> class_, String canonical) {
// well, we haven't found it locally. We're going look it up
if (class_ == ValueSet.class) {
SourcedValueSet svs = null;
if (txCache.hasValueSet(canonical)) {
svs = txCache.getValueSet(canonical);
}
if (svs == null) {
svs = terminologyClientManager.findValueSetOnServer(canonical);
txCache.cacheValueSet(canonical, svs);
}
if (svs != null) {
String web = ToolingExtensions.readStringExtension(svs.getVs(), ToolingExtensions.EXT_WEB_SOURCE);
if (web == null) {
web = Utilities.pathURL(svs.getServer(), "ValueSet", svs.getVs().getIdBase());
}
svs.getVs().setWebPath(web);
svs.getVs().setUserData("External.Link", svs.getServer()); // so we can render it differently
}
if (svs == null) {
return null;
} else {
cacheResource(svs.getVs());
return (T) svs.getVs();
}
} else {
throw new Error("Not supported");
}
}
public <T extends Resource> T findTxResource(Class<T> class_, String canonical, Resource sourceOfReference) {
T result = fetchResource(class_, canonical, sourceOfReference);
if (result == null) {
result = doFindTxResource(class_, canonical);
}
return result;
}
public <T extends Resource> T findTxResource(Class<T> class_, String canonical) {
T result = fetchResource(class_, canonical);
if (result == null) {
result = doFindTxResource(class_, canonical);
}
return result;
}
public <T extends Resource> T findTxResource(Class<T> class_, String canonical, String version) {
T result = fetchResource(class_, canonical, version);
if (result == null) {
result = doFindTxResource(class_, canonical+"|"+version);
}
return result;
}
}

View File

@ -627,4 +627,17 @@ public interface IWorkerContext {
public Set<String> urlsForOid(boolean codeSystem, String oid);
/**
* this first does a fetch resource, and if nothing is found, looks in the
* terminology eco-system for a matching definition for the resource
*
* usually used (and so far only tested with) ValueSet.class
*
* @param value
* @return
*/
public <T extends Resource> T findTxResource(Class<T> class_, String canonical, Resource sourceOfReference);
public <T extends Resource> T findTxResource(Class<T> class_, String canonical);
public <T extends Resource> T findTxResource(Class<T> class_, String canonical, String version);
}

View File

@ -2555,7 +2555,7 @@ public class FHIRPathEngine {
private List<Base> opMemberOf(ExecutionContext context, List<Base> left, List<Base> right, ExpressionNode expr) throws FHIRException {
boolean ans = false;
String url = right.get(0).primitiveValue();
ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) : worker.fetchResource(ValueSet.class, url);
ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) : worker.findTxResource(ValueSet.class, url);
if (vs != null) {
for (Base l : left) {
if (Utilities.existsInList(l.fhirType(), "code", "string", "uri")) {
@ -4620,7 +4620,7 @@ public class FHIRPathEngine {
}
String url = nl.get(0).primitiveValue();
ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) : worker.fetchResource(ValueSet.class, url);
ValueSet vs = hostServices != null ? hostServices.resolveValueSet(this, context.appInfo, url) : worker.findTxResource(ValueSet.class, url);
if (vs == null) {
return new ArrayList<Base>();
}

View File

@ -259,7 +259,11 @@ public class AdditionalBindingsRenderer {
if (binding.compare!=null && binding.valueSet.equals(binding.compare.valueSet))
valueset.style(STYLE_UNCHANGED);
if (br.url != null) {
valueset.ah(determineUrl(br.url), binding.valueSet).tx(br.display);
XhtmlNode a = valueset.ah(determineUrl(br.url), br.uri).tx(br.display);
if (br.external) {
a.tx(" ");
a.img("external.png", null);
}
} else {
valueset.span(null, binding.valueSet).tx(br.display);
}
@ -411,7 +415,7 @@ public class AdditionalBindingsRenderer {
return; // what should happen?
}
BindingResolution br = pkp.resolveBinding(profile, b.getValueSet(), corePath);
XhtmlNode a = children.ahOrCode(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, b.hasDocumentation() ? b.getDocumentation() : null);
XhtmlNode a = children.ahOrCode(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, b.hasDocumentation() ? b.getDocumentation() : br.uri);
if (b.hasDocumentation()) {
a.attribute("title", b.getDocumentation());
}

View File

@ -337,7 +337,7 @@ public class DataRenderer extends Renderer implements CodeResolver {
if ("fr-CA".equals(lang)) {
return "French (Canadian)"; // this one was omitted from the value set
}
ValueSet v = getContext().getWorker().fetchResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages");
ValueSet v = getContext().getWorker().findTxResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages");
if (v != null) {
ConceptReferenceComponent l = null;
for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) {

View File

@ -324,7 +324,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer {
defn.getPieces().add(gen.new Piece(vs.getWebPath(), vs.present(), null));
}
} else {
ValueSet vs = context.getWorker().fetchResource(ValueSet.class, i.getAnswerValueSet(), q);
ValueSet vs = context.getWorker().findTxResource(ValueSet.class, i.getAnswerValueSet(), q);
if (vs == null || !vs.hasWebPath()) {
defn.getPieces().add(gen.new Piece(null, i.getAnswerValueSet(), null));
} else {
@ -513,7 +513,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer {
defn.getPieces().add(gen.new Piece(vs.getWebPath(), vs.present(), null));
}
} else {
ValueSet vs = context.getWorker().fetchResource(ValueSet.class, i.getAnswerValueSet(), q);
ValueSet vs = context.getWorker().findTxResource(ValueSet.class, i.getAnswerValueSet(), q);
if (vs == null || !vs.hasWebPath()) {
defn.getPieces().add(gen.new Piece(null, i.getAnswerValueSet(), null));
} else {
@ -753,7 +753,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer {
ans.ah(vs.getWebPath()).tx(vs.present());
}
} else {
ValueSet vs = context.getWorker().fetchResource(ValueSet.class, i.getAnswerValueSet(), q);
ValueSet vs = context.getWorker().findTxResource(ValueSet.class, i.getAnswerValueSet(), q);
if (vs == null || !vs.hasWebPath()) {
ans.tx(i.getAnswerValueSet());
} else {
@ -854,7 +854,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer {
vs.setUrl(q.getUrl()+"--"+q.getContained(i.getAnswerValueSet().substring(1)));
}
} else {
vs = context.getContext().fetchResource(ValueSet.class, i.getAnswerValueSet(), q);
vs = context.getContext().findTxResource(ValueSet.class, i.getAnswerValueSet(), q);
}
if (vs != null) {
ValueSetExpansionOutcome exp = context.getContext().expandVS(vs, true, false);
@ -975,7 +975,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer {
// content control
defn(tbl, "Max Length", qi.getMaxLength());
if (qi.hasAnswerValueSet()) {
defn(tbl, "Value Set", qi.getDefinition(), context.getWorker().fetchResource(ValueSet.class, qi.getAnswerValueSet(), q));
defn(tbl, "Value Set", qi.getDefinition(), context.getWorker().findTxResource(ValueSet.class, qi.getAnswerValueSet(), q));
}
if (qi.hasAnswerOption()) {
XhtmlNode tr = tbl.tr();

View File

@ -492,7 +492,7 @@ public class QuestionnaireResponseRenderer extends ResourceRenderer {
// ans.ah(vs.getWebPath()).tx(vs.present());
// }
// } else {
// ValueSet vs = context.getWorker().fetchResource(ValueSet.class, i.getAnswerValueSet());
// ValueSet vs = context.getWorker().findTxResource(ValueSet.class, i.getAnswerValueSet());
// if (vs == null || !vs.hasWebPath()) {
// ans.tx(i.getAnswerValueSet());
// } else {
@ -579,7 +579,7 @@ public class QuestionnaireResponseRenderer extends ResourceRenderer {
// vs.setUrl("urn:uuid:"+UUID.randomUUID().toString().toLowerCase());
// }
// } else {
// vs = context.getContext().fetchResource(ValueSet.class, i.getAnswerValueSet());
// vs = context.getContext().findTxResource(ValueSet.class, i.getAnswerValueSet());
// }
// if (vs != null) {
// ValueSetExpansionOutcome exp = context.getContext().expandVS(vs, true, false);
@ -707,7 +707,7 @@ public class QuestionnaireResponseRenderer extends ResourceRenderer {
// // content control
// defn(tbl, "Max Length", qi.getMaxLength());
// if (qi.hasAnswerValueSet()) {
// defn(tbl, "Value Set", qi.getDefinition(), context.getWorker().fetchResource(ValueSet.class, qi.getAnswerValueSet()));
// defn(tbl, "Value Set", qi.getDefinition(), context.getWorker().findTxResource(ValueSet.class, qi.getAnswerValueSet()));
// }
// if (qi.hasAnswerOption()) {
// XhtmlNode tr = tbl.tr();

View File

@ -1550,7 +1550,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
c.addPiece(gen.new Piece("br"));
BindingResolution br = context.getPkp() == null ? makeNullBr(binding) : context.getPkp().resolveBinding(profile, binding, definition.getPath());
c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
c.getPieces().add(checkForNoChange(binding.getValueSetElement(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, br.display, null)));
c.getPieces().add(checkForNoChange(binding.getValueSetElement(), checkAddExternalFlag(br, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, br.display, br.uri))));
if (binding.hasStrength()) {
c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(null, " (", null)));
c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition())));
@ -1662,6 +1662,13 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
return c;
}
private Piece checkAddExternalFlag(BindingResolution br, Piece piece) {
if (br.external) {
piece.setTagImg("external.png");
}
return piece;
}
private boolean isAttr(SourcedElementDefinition ed) {
for (Enumeration<PropertyRepresentation> t : ed.getDefinition().getRepresentation()) {
if (t.getValue() == PropertyRepresentation.XMLATTR) {
@ -2375,10 +2382,11 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
c.addPiece(gen.new Piece("br"));
BindingResolution br = context.getPkp().resolveBinding(profile, binding, definition.getPath());
c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, br.display, null)));
c.getPieces().add(checkForNoChange(binding, checkAddExternalFlag(br, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, br.display, br.uri))));
if (binding.hasStrength()) {
c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), binding.getStrength().toCode(), binding.getStrength().getDefinition()))); c.getPieces().add(gen.new Piece(null, ")", null));
c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), binding.getStrength().toCode(), binding.getStrength().getDefinition())));
c.getPieces().add(gen.new Piece(null, ")", null));
}
if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) {
c.getPieces().add(gen.new Piece(null, ": ", null));
@ -3088,7 +3096,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
c.addPiece(gen.new Piece("br"));
BindingResolution br = context.getPkp().resolveBinding(ed, ved.getBinding(), ved.getPath());
c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, br.display, null)));
c.getPieces().add(checkForNoChange(ved.getBinding(), checkAddExternalFlag(br, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, br.display, br.uri))));
if (ved.getBinding().hasStrength()) {
c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null)));
c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition())));
@ -3470,11 +3478,11 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
return false;
}
public XhtmlNode compareString(String newStr, Base source, String nLink, String name, Base parent, String oldStr, String oLink, int mode) {
public XhtmlNode compareString(String newStr, Base source, String nLink, String name, Base parent, String oldStr, String oLink, int mode, boolean externalN, boolean externalO) {
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
if (mode != GEN_MODE_KEY) {
if (newStr != null) {
renderStatus(source, x).ah(nLink).tx(newStr);
renderStatus(source, x).ah(nLink).txN(newStr).iff(externalN).txN(" ").img("external.png", null);
} else if (VersionComparisonAnnotation.hasDeleted(parent, name)) {
PrimitiveType p = (PrimitiveType) VersionComparisonAnnotation.getDeletedItem(parent, name);
renderStatus(p, x).tx(p.primitiveValue());
@ -3485,33 +3493,33 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
if (newStr==null || newStr.isEmpty()) {
return null;
} else {
renderStatus(source, x).ah(nLink).tx(newStr);
renderStatus(source, x).ah(nLink).txN(newStr).iff(externalN).txN(" ").img("external.png", null);
}
} else if (oldStr!=null && !oldStr.isEmpty() && (newStr==null || newStr.isEmpty())) {
if (mode == GEN_MODE_DIFF) {
return null;
} else {
removed(x).ah(oLink).tx(oldStr);
removed(x).ah(oLink).txN(oldStr).iff(externalO).txN(" ").img("external.png", null);
}
} else if (oldStr.equals(newStr)) {
if (mode==GEN_MODE_DIFF) {
return null;
} else {
unchanged(x).ah(nLink).tx(newStr);
unchanged(x).ah(nLink).txN(newStr).iff(externalN).txN(" ").img("external.png", null);
}
} else if (newStr.startsWith(oldStr)) {
unchanged(x).ah(oLink).tx(oldStr);
renderStatus(source, x).ah(nLink).tx(newStr.substring(oldStr.length()));
unchanged(x).ah(oLink).txN(oldStr).iff(externalO).txN(" ").img("external.png", null);
renderStatus(source, x).ah(nLink).txN(newStr.substring(oldStr.length())).iff(externalN).txN(" ").img("external.png", null);
} else {
// TODO: improve comparision in this fall-through case, by looking for matches in sub-paragraphs?
renderStatus(source, x).ah(nLink).tx(newStr);
removed(x).ah(oLink).tx(oldStr);
renderStatus(source, x).ah(nLink).txN(newStr).iff(externalN).txN(" ").img("external.png", null);
removed(x).ah(oLink).txN(oldStr).iff(externalO).txN(" ").img("external.png", null);
}
return x;
}
public boolean compareString(XhtmlNode x, String newStr, Base source, String nLink, String name, Base parent, String oldStr, String oLink, int mode) {
XhtmlNode x1 = compareString(newStr, source, nLink, name, parent, oldStr, oLink, mode);
public boolean compareString(XhtmlNode x, String newStr, Base source, String nLink, String name, Base parent, String oldStr, String oLink, int mode, boolean externalN, boolean externalO) {
XhtmlNode x1 = compareString(newStr, source, nLink, name, parent, oldStr, oLink, mode, externalN, externalO);
if (x1 == null) {
return false;
} else {
@ -3541,12 +3549,12 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
boolean slicedExtension = d.hasSliceName() && (d.getPath().endsWith(".extension") || d.getPath().endsWith(".modifierExtension"));
// int slicedExtensionMode = (mode == GEN_MODE_KEY) && slicedExtension ? GEN_MODE_SNAP : mode; // see ProfileUtilities.checkExtensionDoco / Task 3970
if (d.hasSliceName()) {
tableRow(tbl, "Slice Name", "profiling.html#slicing", strikethrough, compareString(d.getSliceName(), d.getSliceNameElement(), null, (compare != null ? compare.getSliceName() : null), d, null, "sliceName", mode));
tableRow(tbl, "Slice Constraining", "profiling.html#slicing", strikethrough, compareString(encodeValue(d.getSliceIsConstrainingElement()), d.getSliceIsConstrainingElement(), null, (compare != null ? encodeValue(compare.getSliceIsConstrainingElement()) : null), d, null, "sliceName", mode));
tableRow(tbl, "Slice Name", "profiling.html#slicing", strikethrough, compareString(d.getSliceName(), d.getSliceNameElement(), null, (compare != null ? compare.getSliceName() : null), d, null, "sliceName", mode, false, false));
tableRow(tbl, "Slice Constraining", "profiling.html#slicing", strikethrough, compareString(encodeValue(d.getSliceIsConstrainingElement()), d.getSliceIsConstrainingElement(), null, (compare != null ? encodeValue(compare.getSliceIsConstrainingElement()) : null), d, null, "sliceName", mode, false, false));
}
tableRow(tbl, "Definition", null, strikethrough, compareMarkdown(sd.getName(), d.getDefinitionElement(), (compare==null) || slicedExtension ? null : compare.getDefinitionElement(), mode));
tableRow(tbl, "Short", null, strikethrough, compareString(d.hasShort() ? d.getShort() : null, d.getShortElement(), null, "short", d, compare!= null && compare.hasShortElement() ? compare.getShort() : null, null, mode));
tableRow(tbl, "Short", null, strikethrough, compareString(d.hasShort() ? d.getShort() : null, d.getShortElement(), null, "short", d, compare!= null && compare.hasShortElement() ? compare.getShort() : null, null, mode, false, false));
tableRow(tbl, "Comments", null, strikethrough, compareMarkdown(sd.getName(), d.getCommentElement(), (compare==null) || slicedExtension ? null : compare.getCommentElement(), mode));
tableRow(tbl, "Note", null, strikethrough, businessIdWarning(sd.getName(), tail(d.getPath())));
tableRow(tbl, "Control", "conformance-rules.html#conformance", strikethrough, describeCardinality(d, compare, mode));
@ -3696,13 +3704,13 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
tableRow(tbl, "Summary", "search.html#summary", strikethrough, Boolean.toString(d.getIsSummary()));
}
tableRow(tbl, "Requirements", null, strikethrough, compareMarkdown(sd.getName(), d.getRequirementsElement(), (compare==null) || slicedExtension ? null : compare.getRequirementsElement(), mode));
tableRow(tbl, "Label", null, strikethrough, compareString(d.getLabel(), d.getLabelElement(), null, "label", d, (compare != null ? compare.getLabel() : null), null, mode));
tableRow(tbl, "Label", null, strikethrough, compareString(d.getLabel(), d.getLabelElement(), null, "label", d, (compare != null ? compare.getLabel() : null), null, mode, false, false));
tableRow(tbl, "Alternate Names", null, strikethrough, compareSimpleTypeLists(d.getAlias(), ((compare==null) || slicedExtension ? null : compare.getAlias()), mode));
tableRow(tbl, "Definitional Codes", null, strikethrough, compareDataTypeLists(d.getCode(), ((compare==null) || slicedExtension ? null : compare.getCode()), mode));
tableRow(tbl, "Min Value", null, strikethrough, compareString(d.hasMinValue() ? encodeValue(d.getMinValue()) : null, d.getMinValue(), null, "minValue", d, compare!= null && compare.hasMinValue() ? encodeValue(compare.getMinValue()) : null, null, mode));
tableRow(tbl, "Max Value", null, strikethrough, compareString(d.hasMaxValue() ? encodeValue(d.getMaxValue()) : null, d.getMaxValue(), null, "maxValue", d, compare!= null && compare.hasMaxValue() ? encodeValue(compare.getMaxValue()) : null, null, mode));
tableRow(tbl, "Max Length", null, strikethrough, compareString(d.hasMaxLength() ? toStr(d.getMaxLength()) : null, d.getMaxLengthElement(), null, "maxLength", d, compare!= null && compare.hasMaxLengthElement() ? toStr(compare.getMaxLength()) : null, null, mode));
tableRow(tbl, "Value Required", null, strikethrough, compareString(encodeValue(d.getMustHaveValueElement()), d.getMustHaveValueElement(), null, (compare != null ? encodeValue(compare.getMustHaveValueElement()) : null), d, null, "mustHaveValueElement", mode));
tableRow(tbl, "Min Value", null, strikethrough, compareString(d.hasMinValue() ? encodeValue(d.getMinValue()) : null, d.getMinValue(), null, "minValue", d, compare!= null && compare.hasMinValue() ? encodeValue(compare.getMinValue()) : null, null, mode, false, false));
tableRow(tbl, "Max Value", null, strikethrough, compareString(d.hasMaxValue() ? encodeValue(d.getMaxValue()) : null, d.getMaxValue(), null, "maxValue", d, compare!= null && compare.hasMaxValue() ? encodeValue(compare.getMaxValue()) : null, null, mode, false, false));
tableRow(tbl, "Max Length", null, strikethrough, compareString(d.hasMaxLength() ? toStr(d.getMaxLength()) : null, d.getMaxLengthElement(), null, "maxLength", d, compare!= null && compare.hasMaxLengthElement() ? toStr(compare.getMaxLength()) : null, null, mode, false, false));
tableRow(tbl, "Value Required", null, strikethrough, compareString(encodeValue(d.getMustHaveValueElement()), d.getMustHaveValueElement(), null, (compare != null ? encodeValue(compare.getMustHaveValueElement()) : null), d, null, "mustHaveValueElement", mode, false, false));
tableRow(tbl, "Value Alternatives", null, strikethrough, compareSimpleTypeLists(d.getValueAlternatives(), ((compare==null) || slicedExtension ? null : compare.getValueAlternatives()), mode));
tableRow(tbl, "Default Value", null, strikethrough, encodeValue(d.getDefaultValue(), "defaultValue", d, compare==null ? null : compare.getDefaultValue(), mode));
tableRow(tbl, "Meaning if Missing", null, strikethrough, d.getMeaningWhenMissing());
@ -3716,9 +3724,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
private XhtmlNode presentModifier(ElementDefinition d, int mode, ElementDefinition compare) throws FHIRException, IOException {
XhtmlNode x1 = compareString(encodeValue(d.getIsModifierElement()), d.getIsModifierElement(), null, "isModifier", d, compare == null ? null : encodeValue(compare.getIsModifierElement()), null, mode);
XhtmlNode x1 = compareString(encodeValue(d.getIsModifierElement()), d.getIsModifierElement(), null, "isModifier", d, compare == null ? null : encodeValue(compare.getIsModifierElement()), null, mode, false, false);
if (x1 != null) {
XhtmlNode x2 = compareString(encodeValue(d.getIsModifierReasonElement()), d.getIsModifierReasonElement(), null, "isModifierReason", d, compare == null ? null : encodeValue(compare.getIsModifierReasonElement()), null, mode);
XhtmlNode x2 = compareString(encodeValue(d.getIsModifierReasonElement()), d.getIsModifierReasonElement(), null, "isModifierReason", d, compare == null ? null : encodeValue(compare.getIsModifierReasonElement()), null, mode, false, false);
if (x2 != null) {
x1.tx(" because ");
x1.copyAllContent(x2);
@ -3860,7 +3868,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
DataType au = ToolingExtensions.getAllowedUnits(d);
if (au instanceof CanonicalType) {
String url = ((CanonicalType) au).asStringValue();
ValueSet vs = context.getContext().fetchResource(ValueSet.class, url);
ValueSet vs = context.getContext().findTxResource(ValueSet.class, url);
ret.tx("Value set ");
genCT(ret, url, vs);
return ret;
@ -3951,9 +3959,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
x.codeWithText("This element introduces a set of slices on ", ed.getPath(), ". The slices are ");
String newOrdered = sliceOrderString(slicing);
String oldOrdered = (compare==null || !compare.hasSlicing()) ? null : sliceOrderString(compare.getSlicing());
compareString(x, newOrdered, slicing.getOrderedElement(), null, null, null, oldOrdered, null, mode);
compareString(x, newOrdered, slicing.getOrderedElement(), null, null, null, oldOrdered, null, mode, false, false);
x.tx(" and ");
compareString(x, slicing.hasRules() ? slicing.getRules().getDisplay() : null, slicing.getRulesElement(), null, "rules", slicing, compare!=null && compare.hasSlicing() && compare.getSlicing().hasRules() ? compare.getSlicing().getRules().getDisplay() : null, null, mode);
compareString(x, slicing.hasRules() ? slicing.getRules().getDisplay() : null, slicing.getRulesElement(), null, "rules", slicing, compare!=null && compare.hasSlicing() && compare.getSlicing().hasRules() ? compare.getSlicing().getRules().getDisplay() : null, null, mode, false, false);
if (slicing.hasDiscriminator()) {
x.tx(", and can be differentiated using the following discriminators: ");
@ -4080,11 +4088,11 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
} else {
if (!(mode==GEN_MODE_DIFF && (d.getMin()==compare.getMin() || d.getMin()==0))) {
compareString(x, toStr(d.getMin()), d.getMinElement(), null, "min", d, toStr(compare.getMin()), null, mode);
compareString(x, toStr(d.getMin()), d.getMinElement(), null, "min", d, toStr(compare.getMin()), null, mode, false, false);
}
x.tx("..");
if (!(mode==GEN_MODE_DIFF && (d.getMax().equals(compare.getMax()) || "1".equals(d.getMax())))) {
compareString(x, d.getMax(), d.getMaxElement(), null, "max", d, compare.getMax(), null, mode);
compareString(x, d.getMax(), d.getMaxElement(), null, "max", d, compare.getMax(), null, mode, false, false);
}
}
XhtmlNode t = compareSimpleTypeLists(d.getCondition(), compare == null ? null : compare.getCondition(), mode);
@ -4199,9 +4207,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
boolean ts = false;
if (t.getWorkingCode().startsWith("xs:")) {
ts = compareString(x, t.getWorkingCode(), t, null, "code", t, compare==null ? null : compare.getWorkingCode(), null, mode);
ts = compareString(x, t.getWorkingCode(), t, null, "code", t, compare==null ? null : compare.getWorkingCode(), null, mode, false, false);
} else {
ts = compareString(x, t.getWorkingCode(), t, getTypeLink(t, sd), "code", t, compare==null ? null : compare.getWorkingCode(), compare==null ? null : getTypeLink(compare, sd), mode);
ts = compareString(x, t.getWorkingCode(), t, getTypeLink(t, sd), "code", t, compare==null ? null : compare.getWorkingCode(), compare==null ? null : getTypeLink(compare, sd), mode, false, false);
}
if ((!mustSupportOnly && (t.hasProfile() || (compare!=null && compare.hasProfile()))) || isMustSupport(t.getProfile())) {
@ -4314,7 +4322,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
private XhtmlNode displayBoolean(boolean value, BooleanType source, String name, Base parent, BooleanType compare, int mode) {
String newValue = value ? "true" : source.hasValue() ? "false" : null;
String oldValue = compare==null || compare.getValue()==null ? null : (compare.getValue()!=true ? null : "true");
return compareString(newValue, source, null, name, parent, oldValue, null, mode);
return compareString(newValue, source, null, name, parent, oldValue, null, mode, false, false);
}
@ -4402,16 +4410,32 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
}
private boolean isSimpleContent(XhtmlNode bindingDesc) {
return bindingDesc.getChildNodes().size() == 1 && bindingDesc.getChildNodes().get(0).isPara();
}
private void renderBinding(XhtmlNode span, ElementDefinitionBindingComponent binding, ElementDefinitionBindingComponent compare, String path, StructureDefinition sd, int mode) {
compareString(span, conf(binding), binding.getStrengthElement(), null, "strength", binding, compare == null ? null : conf(compare), null, mode);
compareString(span, conf(binding), binding.getStrengthElement(), null, "strength", binding, compare == null ? null : conf(compare), null, mode, false, false);
span.tx(" ");
BindingResolution br = context.getPkp().resolveBinding(sd, binding, path);
compareString(span, br.display, binding.getValueSetElement(), br.url, "valueSet", binding, compare == null ? null : compare.getValueSet(), null, mode);
compareString(span, br.display, binding.getValueSetElement(), br.url, "valueSet", binding, compare == null ? null : compare.getValueSet(), null, mode, br.external, false);
if (binding.hasStrength() || binding.hasValueSet()) {
span.br();
span.tx("(");
if (binding.hasStrength()) {
span.ah(Utilities.pathURL(corePath, "terminologies.html#"+binding.getStrength().toCode())).tx(binding.getStrength().toCode());
}
if (binding.hasStrength() && binding.hasValueSet()) {
span.tx(" ");
}
if (binding.hasValueSet()) {
span.tx("to ");
XhtmlNode ispan = span.spanClss("copy-text-inline");
ispan.code().tx(binding.getValueSet());
ispan.button("btn-copy", "Click to Copy URL").attribute("data-clipboard-text", binding.getValueSet());
}
span.tx(")");
}
}
private String stripPara(String s) {
@ -4459,7 +4483,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
private XhtmlNode encodeValue(DataType value, String name, Base parent, DataType compare, int mode) throws FHIRException, IOException {
String oldValue = encodeValue(compare);
String newValue = encodeValue(value);
return compareString(newValue, value, null, name, parent, oldValue, null, mode);
return compareString(newValue, value, null, name, parent, oldValue, null, mode, false, false);
}
private String encodeValue(DataType value) throws FHIRException, IOException {
@ -4508,7 +4532,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
}
return compareString(Utilities.escapeXml(newMap), null, null, "mapping", d, Utilities.escapeXml(oldMap), null, mode);
return compareString(Utilities.escapeXml(newMap), null, null, "mapping", d, Utilities.escapeXml(oldMap), null, mode, false, false);
}
private XhtmlNode compareSimpleTypeLists(List<? extends PrimitiveType> original, List<? extends PrimitiveType> compare, int mode) throws IOException {

View File

@ -287,7 +287,7 @@ public abstract class TerminologyRenderer extends ResourceRenderer {
}
CanonicalResource vs = (CanonicalResource) res;
if (vs == null)
vs = getContext().getWorker().fetchResource(ValueSet.class, value, source);
vs = getContext().getWorker().findTxResource(ValueSet.class, value, source);
if (vs == null)
vs = getContext().getWorker().fetchResource(StructureDefinition.class, value, source);
if (vs == null)

View File

@ -118,7 +118,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
re = new ConceptMapRenderInstructions(cm.present(), cm.getUrl(), false);
}
if (re != null) {
ValueSet vst = cm.hasTargetScope() ? getContext().getWorker().fetchResource(ValueSet.class, cm.hasTargetScopeCanonicalType() ? cm.getTargetScopeCanonicalType().getValue() : cm.getTargetScopeUriType().asStringValue(), cm) : null;
ValueSet vst = cm.hasTargetScope() ? getContext().getWorker().findTxResource(ValueSet.class, cm.hasTargetScopeCanonicalType() ? cm.getTargetScopeCanonicalType().getValue() : cm.getTargetScopeUriType().asStringValue(), cm) : null;
res.add(new UsedConceptMap(re, vst == null ? cm.getWebPath() : vst.getWebPath(), cm));
}
}
@ -141,7 +141,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
// Map<ConceptMap, String> mymaps = new HashMap<ConceptMap, String>();
// for (ConceptMap a : context.getWorker().findMapsForSource(vs.getUrl())) {
// String url = "";
// ValueSet vsr = context.getWorker().fetchResource(ValueSet.class, ((Reference) a.getTarget()).getReference());
// ValueSet vsr = context.getWorker().findTxResource(ValueSet.class, ((Reference) a.getTarget()).getReference());
// if (vsr != null)
// url = (String) vsr.getUserData("filename");
// mymaps.put(a, url);
@ -160,7 +160,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
// ConceptMap cm = (ConceptMap) r;
// if (((Reference) cm.getSource()).getReference().equals(cs.getValueSet())) {
// String url = "";
// ValueSet vsr = context.getWorker().fetchResource(ValueSet.class, ((Reference) cm.getTarget()).getReference());
// ValueSet vsr = context.getWorker().findTxResource(ValueSet.class, ((Reference) cm.getTarget()).getReference());
// if (vsr != null)
// url = (String) vsr.getUserData("filename");
// mymaps.put(cm, url);

View File

@ -68,10 +68,42 @@ import org.hl7.fhir.r5.utils.CanonicalResourceUtilities;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.StandardsStatus;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
public class ValueSetUtilities extends TerminologyUtilities {
public static class ValueSetSorter implements Comparator<ValueSet> {
@Override
public int compare(ValueSet o1, ValueSet o2) {
String url1 = o1.getUrl();
String url2 = o2.getUrl();
int c = compareString(url1, url2);
if (c == 0) {
String ver1 = o1.getVersion();
String ver2 = o2.getVersion();
c = VersionUtilities.compareVersions(ver1, ver2);
if (c == 0) {
String d1 = o1.getDateElement().asStringValue();
String d2 = o2.getDateElement().asStringValue();
c = compareString(url1, url2);
}
}
return c;
}
private int compareString(String s1, String s2) {
if (s1 == null) {
return s2 == null ? 0 : 1;
} else {
return s1.compareTo(s2);
}
}
}
public static boolean isServerSide(String url) {
return Utilities.existsInList(url, "http://hl7.org/fhir/sid/cvx");
}
@ -413,7 +445,7 @@ public class ValueSetUtilities extends TerminologyUtilities {
Set<String> systems = new HashSet<>();
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
for (CanonicalType ct : inc.getValueSet()) {
ValueSet vsr = ctxt.fetchResource(ValueSet.class, ct.asStringValue(), vs);
ValueSet vsr = ctxt.findTxResource(ValueSet.class, ct.asStringValue(), vs);
if (vsr != null) {
systems.addAll(listSystems(ctxt, vsr));
}

View File

@ -10,9 +10,20 @@ import org.hl7.fhir.r5.model.TerminologyCapabilities;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.TerminologyClientContextUseCount;
public class TerminologyClientContext {
public enum TerminologyClientContextUseType {
expand, validate, readVS
}
public class TerminologyClientContextUseCount {
private int expands;
private int validates;
private int readVS;
public int getReadVS() {
return readVS;
}
public void setReadVS(int readVS) {
this.readVS = readVS;
}
public int getExpands() {
return expands;
}
@ -52,16 +63,24 @@ public class TerminologyClientContext {
return client;
}
public void seeUse(String s, boolean expand) {
public void seeUse(String s, TerminologyClientContextUseType useType) {
TerminologyClientContextUseCount uc = useCounts.get(s);
if (uc == null) {
uc = new TerminologyClientContextUseCount();
useCounts.put(s,uc);
}
if (expand) {
switch (useType) {
case expand:
uc.expands++;
} else {
break;
case readVS:
uc.readVS++;
break;
case validate:
uc.validates++;
break;
default:
break;
}
}

View File

@ -6,6 +6,7 @@ import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -13,10 +14,18 @@ import java.util.Map;
import java.util.Set;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.CanonicalResourceManager;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r5.model.TerminologyCapabilities;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.TerminologyClientContextUseType;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.ToolingClientLogger;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.model.JsonObject;
@ -133,7 +142,7 @@ public class TerminologyClientManager {
serverList.add(client);
serverMap.put(server, client);
}
client.seeUse(s, expand);
client.seeUse(s, expand ? TerminologyClientContextUseType.expand : TerminologyClientContextUseType.validate);
return client;
}
@ -318,4 +327,83 @@ public class TerminologyClientManager {
this.usage = usage;
}
public SourcedValueSet findValueSetOnServer(String canonical) {
if (IGNORE_TX_REGISTRY || getMasterClient() == null) {
return null;
}
String request = Utilities.pathURL(monitorServiceURL, "resolve?fhirVersion="+factory.getVersion()+"&valueSet="+canonical);
if (usage != null) {
request = request + "&usage="+usage;
}
String server = null;
try {
JsonObject json = JsonParser.parseObjectFromUrl(request);
for (JsonObject item : json.getJsonObjects("authoritative")) {
if (server == null) {
server = item.asString("url");
}
}
for (JsonObject item : json.getJsonObjects("candidate")) {
if (server == null) {
server = item.asString("url");
}
}
if (server == null) {
return null;
}
if (server.contains("://tx.fhir.org")) {
try {
server = server.replace("tx.fhir.org", new URL(getMasterClient().getAddress()).getHost());
} catch (MalformedURLException e) {
}
}
TerminologyClientContext client = serverMap.get(server);
if (client == null) {
try {
client = new TerminologyClientContext(factory.makeClient("id"+(serverList.size()+1), server, getMasterClient().getUserAgent()), false);
} catch (URISyntaxException e) {
throw new TerminologyServiceException(e);
}
serverList.add(client);
serverMap.put(server, client);
}
client.seeUse(canonical, TerminologyClientContextUseType.readVS);
String criteria = canonical.contains("|") ?
"?url="+canonical.substring(0, canonical.lastIndexOf("|"))+"&version="+canonical.substring(canonical.lastIndexOf("|")+1) :
"?url="+canonical;
Bundle bnd = client.getClient().search("ValueSet", criteria);
String rid = null;
if (bnd.getEntry().size() == 0) {
return null;
} else if (bnd.getEntry().size() > 1) {
List<ValueSet> vslist = new ArrayList<>();
for (BundleEntryComponent be : bnd.getEntry()) {
if (be.hasResource() && be.getResource() instanceof ValueSet) {
vslist.add((ValueSet) be.getResource());
}
}
Collections.sort(vslist, new ValueSetUtilities.ValueSetSorter());
rid = vslist.get(vslist.size()-1).getIdBase();
} else {
if (bnd.getEntryFirstRep().hasResource() && bnd.getEntryFirstRep().getResource() instanceof ValueSet) {
rid = bnd.getEntryFirstRep().getResource().getIdBase();
}
}
if (rid == null) {
return null;
}
ValueSet vs = (ValueSet) client.getClient().read("ValueSet", rid);
return new SourcedValueSet(server, vs);
} catch (Exception e) {
String msg = "Error resolving valueSet "+canonical+": "+e.getMessage()+" ("+request+")";
if (!internalErrors.contains(msg)) {
internalErrors.add(msg);
}
if (!monitorServiceURL.contains("tx.fhir.org")) {
e.printStackTrace();
}
return null;
}
}
}

View File

@ -852,7 +852,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
private ValueSet importValueSet(WorkingContext wc, String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError {
if (value == null)
throw fail("unable to find value set with no identity");
ValueSet vs = context.fetchResource(ValueSet.class, value, valueSet);
ValueSet vs = context.findTxResource(ValueSet.class, value, valueSet);
if (vs == null) {
if (context.fetchResource(CodeSystem.class, value, valueSet) != null) {
throw fail("Cannot include value set "+value+" because it's actually a code system");
@ -899,7 +899,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
private ValueSet importValueSetForExclude(WorkingContext wc, String value, ValueSetExpansionComponent exp, Parameters expParams, boolean noInactive, ValueSet valueSet) throws ETooCostly, TerminologyServiceException, FileNotFoundException, IOException, FHIRFormatError {
if (value == null)
throw fail("unable to find value set with no identity");
ValueSet vs = context.fetchResource(ValueSet.class, value, valueSet);
ValueSet vs = context.findTxResource(ValueSet.class, value, valueSet);
if (vs == null) {
if (context.fetchResource(CodeSystem.class, value, valueSet) != null) {
throw fail("Cannot include value set "+value+" because it's actually a code system");

View File

@ -32,6 +32,7 @@ package org.hl7.fhir.r5.terminologies.utilities;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
@ -43,6 +44,7 @@ import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.model.*;
@ -52,7 +54,10 @@ import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.IniFile;
import org.hl7.fhir.utilities.StringPair;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
@ -74,6 +79,25 @@ import com.google.gson.JsonPrimitive;
*/
public class TerminologyCache {
public static class SourcedValueSet {
private String server;
private ValueSet vs;
public SourcedValueSet(String server, ValueSet vs) {
super();
this.server = server;
this.vs = vs;
}
public String getServer() {
return server;
}
public ValueSet getVs() {
return vs;
}
}
public static final boolean TRANSIENT = false;
public static final boolean PERMANENT = true;
private static final String NAME_FOR_NO_SYSTEM = "all-systems";
@ -192,6 +216,7 @@ public class TerminologyCache {
private CapabilityStatement capabilityStatementCache = null;
private TerminologyCapabilities terminologyCapabilitiesCache = null;
private Map<String, NamedCache> caches = new HashMap<String, NamedCache>();
private Map<String, StringPair> vsCache = new HashMap<>();
@Getter @Setter private static boolean noCaching;
@Getter @Setter private static boolean cacheErrors;
@ -247,6 +272,7 @@ public class TerminologyCache {
public void clear() {
caches.clear();
vsCache.clear();
}
public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs, Parameters expParameters) {
@ -703,7 +729,7 @@ public class TerminologyCache {
}
}
private void load() throws FHIRException {
private void load() throws FHIRException, IOException {
for (String fn : new File(folder).list()) {
if (fn.endsWith(CACHE_FILE_EXTENSION) && !fn.equals("validation" + CACHE_FILE_EXTENSION)) {
try {
@ -717,6 +743,14 @@ public class TerminologyCache {
}
}
}
File fini = new File(Utilities.path(folder, "vs-external.ini"));
if (fini.exists()) {
IniFile ini = new IniFile(fini.getAbsolutePath());
for (String k : ini.getPropertyNames("valuesets")) {
String fn = ini.getStringProperty("valuesets", k);
vsCache.put(k, new StringPair(Utilities.noString(fn) ? null : fn, ini.getStringProperty("servers", k)));
}
}
}
private String loadJS(JsonElement e) {
@ -819,5 +853,43 @@ public class TerminologyCache {
return servers;
}
public boolean hasValueSet(String canonical) {
return vsCache.containsKey(canonical);
}
public SourcedValueSet getValueSet(String canonical) {
StringPair sp = vsCache.get(canonical);
if (sp == null || sp.getName() == null) {
return null;
} else {
try {
return new SourcedValueSet(sp.getValue(), (ValueSet) new JsonParser().parse(new FileInputStream(Utilities.path(folder, sp.getName()))));
} catch (Exception e) {
return null;
}
}
}
public void cacheValueSet(String canonical, SourcedValueSet svs) {
try {
if (svs == null) {
vsCache.put(canonical, null);
} else {
String uuid = Utilities.makeUuidLC();
String fn = "vs-"+uuid+".json";
new JsonParser().compose(new FileOutputStream(Utilities.path(folder, fn)), svs.getVs());
vsCache.put(canonical, new StringPair(fn, svs.getServer()));
}
File fini = new File(Utilities.path(folder, "vs-external.ini"));
IniFile ini = new IniFile(fini.getAbsolutePath());
for (String k : vsCache.keySet()) {
ini.setStringProperty("valuesets", k, vsCache.get(k).getName(), null);
ini.setStringProperty("servers", k, vsCache.get(k).getValue(), null);
}
ini.save();
} catch (Exception e) {
}
}
}

View File

@ -522,7 +522,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
if (cs == null) {
OpIssueCode oic = OpIssueCode.NotFound;
IssueType itype = IssueType.NOTFOUND;
ValueSet vs = context.fetchResource(ValueSet.class, system);
ValueSet vs = context.findTxResource(ValueSet.class, system);
if (vs != null) {
warningMessage = context.formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, system);
oic = OpIssueCode.InvalidData;
@ -1453,7 +1453,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
if (inner.containsKey(url)) {
return inner.get(url);
}
ValueSet vs = context.fetchResource(ValueSet.class, url, valueset);
ValueSet vs = context.findTxResource(ValueSet.class, url, valueset);
if (vs == null && info != null) {
unknownValueSets.add(url);
info.addIssue(makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, null, context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_VALUE_SET_, url), OpIssueCode.NotFound, null));

View File

@ -268,6 +268,7 @@ public class ToolingExtensions {
public static final String EXT_SEARCH_PARAMETER_BASE = "http://hl7.org/fhir/tools/StructureDefinition/searchparameter-base-type";
public static final String EXT_ISSUE_SLICE_INFO = "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-slicetext";
public static final String EXT_ISSUE_SERVER = "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-server";
public static final String EXT_WEB_SOURCE = "http://hl7.org/fhir/tools/StructureDefinition/web-source";
// specific extension helpers

View File

@ -97,7 +97,7 @@ public class FHIRPathHostServices implements FHIRPathEngine.IEvaluationContext {
@Override
public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) {
return structureMapUtilities.getWorker().fetchResource(ValueSet.class, url);
return structureMapUtilities.getWorker().findTxResource(ValueSet.class, url);
}
@Override

View File

@ -6,6 +6,7 @@ import java.util.StringJoiner;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.utilities.VersionUtilities.SemVer;
/*
Copyright (c) 2011+, HL7, Inc.
@ -40,6 +41,62 @@ import org.hl7.fhir.exceptions.FHIRException;
public class VersionUtilities {
public static class SemVer {
private String major;
private String minor;
private String patch;
private String label;
public SemVer(String ver) {
String[] p = ver.split("\\.");
if (p.length > 0) {
major = p[0];
}
if (p.length > 1) {
minor = p[1];
}
if (p.length > 2) {
patch = p[2];
if (patch.contains("-")) {
label = patch.substring(patch.indexOf("-")+1);
patch = patch.substring(0, patch.indexOf("-"));
}
}
}
private int compareString(String s1, String s2) {
if (s1 == null) {
return s2 == null ? 0 : 1;
} else {
return s1.compareTo(s2);
}
}
private int compareInteger(String s1, String s2) {
if (s1 == null) {
return s2 == null ? 0 : 1;
} else {
return Integer.compare(Integer.parseInt(s1), Integer.parseInt(s2));
}
}
public int compareTo(SemVer sv2) {
int c = compareInteger(major, sv2.major);
if (c == 0) {
c = compareInteger(minor, sv2.minor);
}
if (c == 0) {
c = compareInteger(patch, sv2.patch);
}
if (c == 0) {
c = compareString(label, sv2.label);
}
return c;
}
}
public static final String[] SUPPORTED_MAJOR_VERSIONS = {"1.0", "1.4", "3.0", "4.0", "5.0", "6.0"};
public static final String[] SUPPORTED_VERSIONS = {"1.0.2", "1.4.0", "3.0.2", "4.0.1", "4.1.0", "4.3.0", "5.0.0", "6.0.0"};
@ -650,5 +707,17 @@ public class VersionUtilities {
return version != null && version.startsWith("6.");
}
public static int compareVersions(String ver1, String ver2) {
if (ver1 == null) {
return ver2 == null ? 0 : 1;
} else if (isSemVer(ver1) && isSemVer(ver2)) {
SemVer sv1 = new SemVer(ver1);
SemVer sv2 = new SemVer(ver2);
return sv1.compareTo(sv2);
} else {
return ver1.compareTo(ver2);
}
}
}

View File

@ -141,6 +141,7 @@ public class HierarchicalTableGenerator extends TranslatingUtilities {
private String text;
private String hint;
private String style;
private String tagImg;
private Map<String, String> attributes;
private XhtmlNodeList children;
@ -231,6 +232,16 @@ public class HierarchicalTableGenerator extends TranslatingUtilities {
attributes.put(name, value);
return this;
}
public String getTagImg() {
return tagImg;
}
public Piece setTagImg(String tagImg) {
this.tagImg = tagImg;
return this;
}
}
public class Cell {
@ -879,6 +890,14 @@ public class HierarchicalTableGenerator extends TranslatingUtilities {
a.addChildren(p.getChildren());
}
addStyle(a, p);
if (p.getTagImg() != null) {
a.tx(" ");
a.img(p.getTagImg(), null);
}
if (p.hasChildren()) {
tc.getChildNodes().addAll(p.getChildren());
}
} else {
if (!Utilities.noString(p.getHint())) {
XhtmlNode s = addStyle(tc.addTag("span"), p);
@ -893,6 +912,10 @@ public class HierarchicalTableGenerator extends TranslatingUtilities {
if (p.hasChildren()) {
tc.getChildNodes().addAll(p.getChildren());
}
if (p.getTagImg() != null) {
tc.tx(" ");
tc.img(p.getTagImg(), null);
}
}
}
if (makeTargets && !Utilities.noString(anchor))

View File

@ -181,7 +181,11 @@ public abstract class XhtmlFluent {
}
public XhtmlNode img(String src, String alt) {
return addTag("img").attribute("src", src).attribute("alt", alt);
if (alt == null) {
return addTag("img").attribute("src", src);
} else {
return addTag("img").attribute("src", src).attribute("alt", alt);
}
}
public XhtmlNode img(String src, String alt, String title) {
@ -276,6 +280,18 @@ public abstract class XhtmlFluent {
}
}
// differs from tx because it returns the owner node, not the created text
public XhtmlFluent txN(String cnt) {
addText(cnt);
return this;
}
public XhtmlFluent iff(boolean test) {
if (test) {
return this;
} else {
return new XhtmlNode(NodeType.Element, "span"); // which will never be connected
}
}
}

View File

@ -783,11 +783,6 @@ public class XhtmlNode extends XhtmlFluent implements IBaseXhtml {
return setAttribute("colspan", Integer.toString(n));
}
// differs from tx because it returns the owner node, not the created text
public XhtmlNode txN(String cnt) {
addText(cnt);
return this;
}
@Override
@ -938,5 +933,31 @@ public class XhtmlNode extends XhtmlFluent implements IBaseXhtml {
}
return x;
}
// differs from tx because it returns the owner node, not the created text
public XhtmlNode txN(String cnt) {
addText(cnt);
return this;
}
public XhtmlNode iff(boolean test) {
if (test) {
return this;
} else {
return new XhtmlNode(NodeType.Element, "span"); // which will never be connected
}
}
public XhtmlNode button(String class_, String title) {
XhtmlNode btn = addTag("button");
btn.attribute("class", class_);
if (title != null) {
btn.attribute("title", title);
}
return btn;
}
}

View File

@ -978,11 +978,11 @@ public class BaseValidator implements IValidationContextResourceLoader {
return null;
} else {
long t = System.nanoTime();
ValueSet fr = context.fetchResource(ValueSet.class, reference, src);
ValueSet fr = context.findTxResource(ValueSet.class, reference, src);
if (fr == null) {
if (!Utilities.isAbsoluteUrl(reference)) {
reference = resolve(uri, reference);
fr = context.fetchResource(ValueSet.class, reference, src);
fr = context.findTxResource(ValueSet.class, reference, src);
}
}
if (fr == null) {

View File

@ -537,7 +537,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
return null;
}
return context.fetchResource(ValueSet.class, url);
return context.findTxResource(ValueSet.class, url);
}
@Override
@ -1871,7 +1871,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
// private String describeValueSet(String url) {
// ValueSet vs = context.fetchResource(ValueSet.class, url);
// ValueSet vs = context.findTxResource(ValueSet.class, url);
// if (vs != null) {
// return "'"+vs.present()+"' ("+url+")";
// } else {

View File

@ -194,10 +194,10 @@ public class ConceptMapValidator extends BaseValidator {
if (ref.contains("|")) {
res.url = ref.substring(0, ref.indexOf("|"));
res.version = ref.substring(ref.indexOf("|")+1);
res.vs = context.fetchResource(ValueSet.class, res.url, res.version);
res.vs = context.findTxResource(ValueSet.class, res.url, res.version);
} else {
res.url = ref;
res.vs = context.fetchResource(ValueSet.class, res.url);
res.vs = context.findTxResource(ValueSet.class, res.url);
}
return res;
}

View File

@ -894,7 +894,7 @@ public class StructureMapValidator extends BaseValidator {
ValueSet srcVS = null;
if (srcED != null) {
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcED.getBinding().hasValueSet() && srcED.getBinding().getStrength() == BindingStrength.REQUIRED, I18nConstants.SM_TARGET_TRANSLATE_BINDING_SOURCE)) {
srcVS = context.fetchResource(ValueSet.class, srcED.getBinding().getValueSet());
srcVS = context.findTxResource(ValueSet.class, srcED.getBinding().getValueSet());
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcVS != null, I18nConstants.SM_TARGET_TRANSLATE_BINDING_VS_SOURCE)) {
ValueSetExpansionOutcome vse = context.expandVS(srcVS, true, false);
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vse.isOk(), I18nConstants.SM_TARGET_TRANSLATE_BINDING_VSE_SOURCE, vse.getError())) {
@ -913,7 +913,7 @@ public class StructureMapValidator extends BaseValidator {
}
if (srcED != null) {
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, tgtED.getBinding().hasValueSet() && tgtED.getBinding().getStrength() == BindingStrength.REQUIRED, I18nConstants.SM_TARGET_TRANSLATE_BINDING_TARGET)) {
ValueSet vs = context.fetchResource(ValueSet.class, tgtED.getBinding().getValueSet());
ValueSet vs = context.findTxResource(ValueSet.class, tgtED.getBinding().getValueSet());
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vs != null, I18nConstants.SM_TARGET_TRANSLATE_BINDING_VS_TARGET)) {
ValueSetExpansionOutcome vse = context.expandVS(vs, true, false);
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vse.isOk(), I18nConstants.SM_TARGET_TRANSLATE_BINDING_VSE_TARGET, vse.getError())) {

View File

@ -123,7 +123,7 @@ public class ValueSetValidator extends BaseValidator {
int i = 0;
for (Element ve : valuesets) {
String v = ve.getValue();
ValueSet vs = context.fetchResource(ValueSet.class, v);
ValueSet vs = context.findTxResource(ValueSet.class, v);
if (vs == null) {
NodeStack ns = stack.push(ve, i, ve.getProperty().getDefinition(), ve.getProperty().getDefinition());