diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java index 80c9f8375..45f3514ed 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonRenderer.java @@ -111,11 +111,15 @@ public class ComparisonRenderer implements IEvaluationContext { } private void dumpBinaries() throws IOException { - for (String k : contextLeft.getBinaries().keySet()) { - TextFile.bytesToFile(contextLeft.getBinaries().get(k), Utilities.path(folder, k)); + if (contextLeft != null && contextLeft.getBinaries() != null) { + for (String k : contextLeft.getBinaries().keySet()) { + TextFile.bytesToFile(contextLeft.getBinaries().get(k), Utilities.path(folder, k)); + } } - for (String k : contextRight.getBinaries().keySet()) { - TextFile.bytesToFile(contextRight.getBinaries().get(k), Utilities.path(folder, k)); + if (contextRight != null && contextRight.getBinaries() != null) { + for (String k : contextRight.getBinaries().keySet()) { + TextFile.bytesToFile(contextRight.getBinaries().get(k), Utilities.path(folder, k)); + } } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java index 32abc1a8a..65852093e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java @@ -109,6 +109,7 @@ import org.hl7.fhir.r5.renderers.spreadsheets.SpreadsheetGenerator; import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; import org.hl7.fhir.r5.utils.FHIRLexer; import org.hl7.fhir.r5.utils.FHIRPathEngine; +import org.hl7.fhir.r5.utils.PublicationHacker; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.TranslatingUtilities; import org.hl7.fhir.r5.utils.XVerExtensionManager; @@ -666,6 +667,7 @@ public class ProfileUtilities extends TranslatingUtilities { throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_, derived.getSnapshot().getElementFirstRep().getPath(), derived.getUrl(), base.getUrl())); updateMaps(base, derived); + setIds(derived, false); if (debug) { System.out.println("Differential: "); for (ElementDefinition ed : derived.getDifferential().getElement()) @@ -674,7 +676,6 @@ public class ProfileUtilities extends TranslatingUtilities { for (ElementDefinition ed : derived.getSnapshot().getElement()) System.out.println(" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); } - setIds(derived, false); CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); //Check that all differential elements have a corresponding snapshot element int ce = 0; @@ -702,10 +703,10 @@ public class ProfileUtilities extends TranslatingUtilities { if (!debug) { System.out.println("Differential: "); for (ElementDefinition ed : derived.getDifferential().getElement()) - System.out.println(" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); + System.out.println(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); System.out.println("Snapshot: "); for (ElementDefinition ed : derived.getSnapshot().getElement()) - System.out.println(" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); + System.out.println(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed)); } if (exception) throw new DefinitionException(msg); @@ -1053,7 +1054,7 @@ public class ProfileUtilities extends TranslatingUtilities { private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit, int diffLimit, String url, String webUrl, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone, ElementDefinition slicer, String typeSlicingPath, List redirector, StructureDefinition srcSD) throws DefinitionException, FHIRException { if (debug) { - System.out.println(indent+"PP @ "+resultPathBase+" / "+contextPathSrc+" : base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+", redirector = "+(redirector == null ? "null" : redirector.toString())+")"); + System.out.println(indent+"PP @ "+resultPathBase+" / "+contextPathSrc+" : base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+", k "+(redirector == null ? "null" : redirector.toString())+")"); } ElementDefinition res = null; List typeList = new ArrayList<>(); @@ -1088,7 +1089,7 @@ public class ProfileUtilities extends TranslatingUtilities { processPaths(indent+" ", result, base, differential, baseCursor+1, diffCursor, baseLimit, diffLimit, url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD); baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor+1, baseLimit); } else { - if (outcome.getType().size() == 0) { + if (outcome.getType().size() == 0 && !outcome.hasContentReference()) { throw new DefinitionException(context.formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, cpath, differential.getElement().get(diffCursor).getPath(), profileName)); } boolean nonExtension = false; @@ -1103,19 +1104,46 @@ public class ProfileUtilities extends TranslatingUtilities { } } } - if (nonExtension) { - throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, cpath, differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName)); - } - StructureDefinition dt = outcome.getType().size() > 1 ? context.fetchTypeDefinition("Element") : getProfileForDataType(outcome.getType().get(0)); - if (dt == null) { - throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), cpath)); - } - contextName = dt.getUrl(); int start = diffCursor; while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+".")) diffCursor++; - processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, - diffCursor-1, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD); + if (nonExtension) { + throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, cpath, differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName)); + } + if (outcome.hasContentReference()) { + ElementDefinitionResolution tgt = getElementById(srcSD, base.getElement(), outcome.getContentReference()); + if (tgt == null) + throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_REFERENCE_TO_, outcome.getContentReference())); + replaceFromContentReference(outcome, tgt.getElement()); + if (tgt.getSource() != srcSD) { + base = tgt.getSource().getSnapshot(); + int nbc = base.getElement().indexOf(tgt.getElement())+1; + int nbl = nbc; + while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getElement().getPath()+".")) + nbl++; + processPaths(indent+" ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, webUrl, profileName, tgt.getElement().getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, outcome, cpath), tgt.getSource()); + } else { + int nbc = base.getElement().indexOf(tgt.getElement())+1; + int nbl = nbc; + while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getElement().getPath()+".")) + nbl++; + System.out.println("Test!"); + processPaths(indent+" ", result, base, differential, nbc, start, nbl-1, diffCursor-1, url, webUrl, profileName, tgt.getElement().getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, outcome, cpath), srcSD); + } + } else { + StructureDefinition dt = outcome.getType().size() > 1 ? context.fetchTypeDefinition("Element") : getProfileForDataType(outcome.getType().get(0)); + if (dt == null) { + throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), cpath)); + } + contextName = dt.getUrl(); + if (redirector.isEmpty()) { + processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, + diffCursor-1, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD); + } else { + processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, + diffCursor-1, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, currentBase, cpath), srcSD); + } + } } } baseCursor++; @@ -1716,8 +1744,9 @@ public class ProfileUtilities extends TranslatingUtilities { updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing()); updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url, srcSD); // if there's no slice, we don't want to update the unsliced description removeStatusExtensions(outcome); - } else if (!diffMatches.get(0).hasSliceName()) + } else if (!diffMatches.get(0).hasSliceName()) { diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called + } result.getElement().add(outcome); @@ -2314,7 +2343,12 @@ public class ProfileUtilities extends TranslatingUtilities { return pathSimple; // String ptail = pathSimple.substring(contextPath.length() + 1); if (redirector.size() > 0) { - String ptail = pathSimple.substring(contextPath.length()+1); + String ptail = null; + if (contextPath.length() >= pathSimple.length()) { + ptail = pathSimple.substring(pathSimple.indexOf(".")+1); + } else { + ptail = pathSimple.substring(contextPath.length()+1); + } return redirector.get(redirector.size()-1).getPath()+"."+ptail; // return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1); } else { @@ -2329,7 +2363,12 @@ public class ProfileUtilities extends TranslatingUtilities { s = pathSimple; else { if (redirector.size() > 0) { - String ptail = pathSimple.substring(redirectSource.length() + 1); + String ptail = null; + if (redirectSource.length() >= pathSimple.length()) { + ptail = pathSimple.substring(pathSimple.indexOf(".")+1); + } else { + ptail = pathSimple.substring(redirectSource.length()+1); + } // ptail = ptail.substring(ptail.indexOf(".")+1); s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail; } else { @@ -2493,6 +2532,9 @@ public class ProfileUtilities extends TranslatingUtilities { if (VersionUtilities.isR2Ver(context.getVersion())) { return "http://hl7.org/fhir/DSTU2/"; } + if (VersionUtilities.isR4BVer(context.getVersion())) { + return "http://hl7.org/fhir/2021Mar/"; + } return ""; } @@ -3346,7 +3388,7 @@ public class ProfileUtilities extends TranslatingUtilities { } if (ved.getBinding().hasDescription() && MarkDownProcessor.isSimpleMarkdown(ved.getBinding().getDescription())) { c.getPieces().add(gen.new Piece(null, ": ", null)); - c.addMarkdownNoPara(ved.getBinding().getDescription()); + c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context, ved.getBinding().getDescriptionElement()).asStringValue()); } } c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null)); @@ -4584,7 +4626,7 @@ public class ProfileUtilities extends TranslatingUtilities { } if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) { c.getPieces().add(gen.new Piece(null, ": ", null)); - c.addMarkdownNoPara(binding.getDescription(), checkForNoChange(binding.getDescriptionElement())); + c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context, binding.getDescriptionElement()).asStringValue(), checkForNoChange(PublicationHacker.fixBindingDescriptions(context, binding.getDescriptionElement()))); } } for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { @@ -4937,7 +4979,7 @@ public class ProfileUtilities extends TranslatingUtilities { } if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) { c.getPieces().add(gen.new Piece(null, ": ", null)); - c.addMarkdownNoPara(binding.getDescription()); + c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context, binding.getDescriptionElement()).asStringValue()); } } for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java index 54785abcb..d306c414b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java @@ -105,6 +105,8 @@ public class Element extends Base { private boolean hasParentForValidator; private String path; private List messages; + private boolean prohibited; + private boolean required; public Element(String name) { super(); @@ -975,4 +977,26 @@ public class Element extends Base { public List getMessages() { return messages; } + + public void removeChild(String name) { + children.removeIf(n -> name.equals(n.getName())); + } + + public boolean isProhibited() { + return prohibited; + } + + public void setProhibited(boolean prohibited) { + this.prohibited = prohibited; + } + + public boolean isRequired() { + return required; + } + + public void setRequired(boolean required) { + this.required = required; + } + + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java index f2a3418e0..d4b633e02 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java @@ -36,6 +36,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; @@ -108,8 +109,9 @@ public class JsonParser extends ParserBase { @Override - public Element parse(InputStream stream) throws IOException, FHIRException { + public List parse(InputStream stream) throws IOException, FHIRException { // if we're parsing at this point, then we're going to use the custom parser + List res = new ArrayList<>(); map = new IdentityHashMap(); String source = TextFile.streamToString(stream); if (policy == ValidationPolicy.EVERYTHING) { @@ -121,12 +123,19 @@ public class JsonParser extends ParserBase { return null; } assert (map.containsKey(obj)); - return parse(obj); + Element e = parse(obj); + if (e != null) { + res.add(new NamedElement(null, e)); + } } else { JsonObject obj = JsonTrackingParser.parse(source, null); // (JsonObject) new com.google.gson.JsonParser().parse(source); // assert (map.containsKey(obj)); - return parse(obj); + Element e = parse(obj); + if (e != null) { + res.add(new NamedElement(null, e)); + } } + return res; } public Element parse(JsonObject object, Map map) throws FHIRException { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Manager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Manager.java index 658789419..be9918f74 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Manager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Manager.java @@ -34,18 +34,21 @@ package org.hl7.fhir.r5.elementmodel; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.List; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.model.StructureDefinition; public class Manager { //TODO use EnumMap - public enum FhirFormat { XML, JSON, TURTLE, TEXT, VBAR; + public enum FhirFormat { XML, JSON, TURTLE, TEXT, VBAR, SHC; + // SHC = smart health cards, including as text versions of QR codes public String getExtension() { switch (this) { @@ -82,10 +85,15 @@ public class Manager { } - public static Element parse(IWorkerContext context, InputStream source, FhirFormat inputFormat) throws FHIRFormatError, DefinitionException, IOException, FHIRException { + public static List parse(IWorkerContext context, InputStream source, FhirFormat inputFormat) throws FHIRFormatError, DefinitionException, IOException, FHIRException { return makeParser(context, inputFormat).parse(source); } + public static Element parseSingle(IWorkerContext context, InputStream source, FhirFormat inputFormat) throws FHIRFormatError, DefinitionException, IOException, FHIRException { + return makeParser(context, inputFormat).parseSingle(source); + } + + public static void compose(IWorkerContext context, Element e, OutputStream destination, FhirFormat outputFormat, OutputStyle style, String base) throws FHIRException, IOException { makeParser(context, outputFormat).compose(e, destination, style, base); } @@ -96,6 +104,7 @@ public class Manager { case XML : return new XmlParser(context); case TURTLE : return new TurtleParser(context); case VBAR : return new VerticalBarParser(context); + case SHC : return new SHCParser(context); case TEXT : throw new Error("Programming logic error: do not call makeParser for a text resource"); } return null; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ObjectConverter.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ObjectConverter.java index 905961585..719f54236 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ObjectConverter.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ObjectConverter.java @@ -38,6 +38,7 @@ import java.util.List; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r5.conformance.ProfileUtilities; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CodeableConcept; @@ -70,7 +71,11 @@ public class ObjectConverter { org.hl7.fhir.r5.formats.JsonParser jp = new org.hl7.fhir.r5.formats.JsonParser(); jp.compose(bs, ig); ByteArrayInputStream bi = new ByteArrayInputStream(bs.toByteArray()); - return new JsonParser(context).parse(bi); + List list = new JsonParser(context).parse(bi); + if (list.size() != 1) { + throw new FHIRException("Unable to convert because the source contains multieple resources"); + } + return list.get(0).getElement(); } public Element convert(Property property, DataType type) throws FHIRException { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ParserBase.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ParserBase.java index 46497484d..c3d2a3023 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ParserBase.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ParserBase.java @@ -40,6 +40,7 @@ import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement; import org.hl7.fhir.r5.formats.FormatUtilities; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.model.StructureDefinition; @@ -54,6 +55,23 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.Source; public abstract class ParserBase { + public class NamedElement { + private String name; + private Element element; + public NamedElement(String name, Element element) { + super(); + this.name = name; + this.element = element; + } + public String getName() { + return name; + } + public Element getElement() { + return element; + } + + } + public interface ILinkResolver { String resolveType(String type); String resolveProperty(Property property); @@ -86,7 +104,15 @@ public abstract class ParserBase { this.errors = errors; } - public abstract Element parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException; + public abstract List parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException; + + public Element parseSingle(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException { + List res = parse(stream); + if (res.size() != 1) { + throw new FHIRException("Parsing FHIR content returned multiple elements in a context where only one element is allowed"); + } + return res.get(0).getElement(); + } public abstract void compose(Element e, OutputStream destination, OutputStyle style, String base) throws FHIRException, IOException; @@ -161,5 +187,9 @@ public abstract class ParserBase { this.showDecorations = showDecorations; } + public String getImpliedProfile() { + return null; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java new file mode 100644 index 000000000..c38e8790e --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/SHCParser.java @@ -0,0 +1,310 @@ +package org.hl7.fhir.r5.elementmodel; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.formats.IParser.OutputStyle; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.utilities.json.JSONUtil; +import org.hl7.fhir.utilities.json.JsonTrackingParser; +import org.hl7.fhir.utilities.json.JsonTrackingParser.LocationData; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +/** + * this class is actually a smart health cards validator. + * It's going to parse the JWT and assume that it contains + * a smart health card, which has a nested bundle in it, and + * then validate the bundle. + * + * See https://spec.smarthealth.cards/#health-cards-are-encoded-as-compact-serialization-json-web-signatures-jws + * + * This parser dose the JWT work, and then passes the JsonObject through to the underlying JsonParser + * + * Error locations are in the decoded payload + * + * @author grahame + * + */ +public class SHCParser extends ParserBase { + + private JsonParser jsonParser; + private Map map; + private List types = new ArrayList<>(); + + public SHCParser(IWorkerContext context) { + super(context); + jsonParser = new JsonParser(context); + } + + public List parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException { + List res = new ArrayList<>(); + String src = TextFile.streamToString(stream).trim(); + List list = new ArrayList<>(); + String pfx = null; + if (src.startsWith("{")) { + JsonObject json = JsonTrackingParser.parseJson(src); + if (checkProperty(json, "$", "verifiableCredential", true, "Array")) { + pfx = "verifiableCredential"; + JsonArray arr = json.getAsJsonArray("verifiableCredential"); + int i = 0; + for (JsonElement e : arr) { + if (!(e instanceof JsonPrimitive)) { + logError(line(e), col(e), "$.verifiableCredential["+i+"]", IssueType.STRUCTURE, "Wrong Property verifiableCredential in JSON Payload. Expected : String but found "+JSONUtil.type(e), IssueSeverity.ERROR); + } else { + list.add(e.getAsString()); + } + i++; + } + } else { + return res; + } + } else { + list.add(src); + } + int c = 0; + for (String ssrc : list) { + String prefix = pfx == null ? "" : pfx+"["+Integer.toString(c)+"]."; + c++; + JWT jwt = null; + try { + jwt = decodeJWT(ssrc); + } catch (Exception e) { + logError(1, 1, prefix+"JWT", IssueType.INVALID, "Unable to decode JWT token", IssueSeverity.ERROR); + return res; + } + map = jwt.map; + JsonTrackingParser.write(jwt.payload, "c:\\temp\\payload.json"); + checkNamedProperties(jwt.getPayload(), prefix+"payload", "iss", "nbf", "vc"); + checkProperty(jwt.getPayload(), prefix+"payload", "iss", true, "String"); + logError(1, 1, prefix+"JWT", IssueType.INFORMATIONAL, "The FHIR Validator does not check the JWT signature "+ + "(see https://demo-portals.smarthealth.cards/VerifierPortal.html or https://github.com/smart-on-fhir/health-cards-dev-tools) (Issuer = '"+jwt.getPayload().get("iss").getAsString()+"')", IssueSeverity.INFORMATION); + checkProperty(jwt.getPayload(), prefix+"payload", "nbf", true, "Number"); + JsonObject vc = jwt.getPayload().getAsJsonObject("vc"); + if (vc == null) { + logError(1, 1, "JWT", IssueType.STRUCTURE, "Unable to find property 'vc' in the payload", IssueSeverity.ERROR); + return res; + } + String path = prefix+"payload.vc"; + checkNamedProperties(vc, path, "type", "credentialSubject"); + if (!checkProperty(vc, path, "type", true, "Array")) { + return res; + } + JsonArray type = vc.getAsJsonArray("type"); + int i = 0; + for (JsonElement e : type) { + if (!(e instanceof JsonPrimitive)) { + logError(line(e), col(e), path+".type["+i+"]", IssueType.STRUCTURE, "Wrong Property Type in JSON Payload. Expected : String but found "+JSONUtil.type(e), IssueSeverity.ERROR); + } else { + types.add(e.getAsString()); + } + i++; + } + if (!types.contains("https://smarthealth.cards#health-card")) { + logError(line(vc), col(vc), path, IssueType.STRUCTURE, "Card does not claim to be of type https://smarthealth.cards#health-card, cannot validate", IssueSeverity.ERROR); + return res; + } + if (!checkProperty(vc, path, "credentialSubject", true, "Object")) { + return res; + } + JsonObject cs = vc.getAsJsonObject("credentialSubject"); + path = path+".credentialSubject"; + if (!checkProperty(cs, path, "fhirVersion", true, "String")) { + return res; + } + JsonElement fv = cs.get("fhirVersion"); + if (!VersionUtilities.versionsCompatible(context.getVersion(), fv.getAsString())) { + logError(line(fv), col(fv), path+".fhirVersion", IssueType.STRUCTURE, "Card claims to be of version "+fv.getAsString()+", cannot be validated against version "+context.getVersion(), IssueSeverity.ERROR); + return res; + } + if (!checkProperty(cs, path, "fhirBundle", true, "Object")) { + return res; + } + // ok. all checks passed, we can now validate the bundle + Element e = jsonParser.parse(cs.getAsJsonObject("fhirBundle"), map); + if (e != null) { + res.add(new NamedElement(path, e)); + } + } + return res; + } + + + @Override + public String getImpliedProfile() { + if (types.contains("https://smarthealth.cards#covid19") && types.contains("https://smarthealth.cards#immunization")) { + return "http://hl7.org/fhir/uv/shc-vaccination/StructureDefinition/shc-vaccination-bundle-dm"; + } + if (types.contains("https://smarthealth.cards#covid19") && types.contains("https://smarthealth.cards#laboratory")) { + return "http://hl7.org/fhir/uv/shc-vaccination/StructureDefinition/shc-covid19-laboratory-bundle-dm"; + } + if (types.contains("https://smarthealth.cards#laboratory")) { + return "http://hl7.org/fhir/uv/shc-vaccination/StructureDefinition/shc-infectious-disease-laboratory-bundle-dm"; + } + return null; + } + + + private boolean checkProperty(JsonObject obj, String path, String name, boolean required, String type) { + JsonElement e = obj.get(name); + if (e != null) { + String t = JSONUtil.type(e); + if (!type.equals(t)) { + logError(line(e), col(e), path+"."+name, IssueType.STRUCTURE, "Wrong Property Type in JSON Payload. Expected : "+type+" but found "+t, IssueSeverity.ERROR); + } else { + return true; + } + } else if (required) { + logError(line(obj), col(obj), path, IssueType.STRUCTURE, "Missing Property in JSON Payload: "+name, IssueSeverity.ERROR); + } else { + return true; + } + return false; + } + + private void checkNamedProperties(JsonObject obj, String path, String... names) { + for (Entry e : obj.entrySet()) { + if (!Utilities.existsInList(e.getKey(), names)) { + logError(line(e.getValue()), col(e.getValue()), path+"."+e.getKey(), IssueType.STRUCTURE, "Unknown Property in JSON Payload", IssueSeverity.WARNING); + } + } + } + + private int line(JsonElement e) { + if (map == null|| !map.containsKey(e)) + return -1; + else + return map.get(e).getLine(); + } + + private int col(JsonElement e) { + if (map == null|| !map.containsKey(e)) + return -1; + else + return map.get(e).getCol(); + } + + + + public void compose(Element e, OutputStream destination, OutputStyle style, String base) throws FHIRException, IOException { + throw new FHIRFormatError("Writing resources is not supported for the SHC format"); + // because then we'd have to try to sign, and we're just not going to be doing that from the element model + } + + + public static class JWT { + + private JsonObject header; + private JsonObject payload; + public Map map = new HashMap<>(); + + public JsonObject getHeader() { + return header; + } + public void setHeader(JsonObject header) { + this.header = header; + } + public JsonObject getPayload() { + return payload; + } + public void setPayload(JsonObject payload) { + this.payload = payload; + } + } + + private static final int BUFFER_SIZE = 1024; + public static final String CURRENT_PACKAGE = "hl7.fhir.uv.shc-vaccination#0.6.2"; + + // todo: deal with chunking + public static String decodeQRCode(String src) { + StringBuilder b = new StringBuilder(); + if (!src.startsWith("shc:/")) { + throw new FHIRException("Unable to process smart health card (didn't start with shc:/)"); + } + for (int i = 5; i < src.length(); i = i + 2) { + String s = src.substring(i, i+2); + byte v = Byte.parseByte(s); + char c = (char) (45+v); + b.append(c); + } + return b.toString(); + } + + public static JWT decodeJWT(String jwt) throws IOException, DataFormatException { + if (jwt.startsWith("shc:/")) { + jwt = decodeQRCode(jwt); + } + String[] parts = splitToken(jwt); + byte[] headerJson; + byte[] payloadJson; + try { + headerJson = Base64.getUrlDecoder().decode(parts[0]); + payloadJson = Base64.getUrlDecoder().decode(parts[1]); + } catch (NullPointerException e) { + throw new FHIRException("The UTF-8 Charset isn't initialized.", e); + } catch (IllegalArgumentException e){ + throw new FHIRException("The input is not a valid base 64 encoded string.", e); + } + JWT res = new JWT(); + res.header = JsonTrackingParser.parseJson(headerJson); + if ("DEF".equals(JSONUtil.str(res.header, "zip"))) { + payloadJson = inflate(payloadJson); + } + res.payload = JsonTrackingParser.parse(TextFile.bytesToString(payloadJson), res.map, true); + return res; + } + + static String[] splitToken(String token) { + String[] parts = token.split("\\."); + if (parts.length == 2 && token.endsWith(".")) { + //Tokens with alg='none' have empty String as Signature. + parts = new String[]{parts[0], parts[1], ""}; + } + if (parts.length != 3) { + throw new FHIRException(String.format("The token was expected to have 3 parts, but got %s.", parts.length)); + } + return parts; + } + + public static final byte[] inflate(byte[] data) throws IOException, DataFormatException { + final Inflater inflater = new Inflater(true); + inflater.setInput(data); + + try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length)) + { + byte[] buffer = new byte[BUFFER_SIZE]; + while (!inflater.finished()) + { + final int count = inflater.inflate(buffer); + outputStream.write(buffer, 0, count); + } + + return outputStream.toByteArray(); + } + } + + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Tester.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Tester.java index 52e68067a..03248a326 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Tester.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Tester.java @@ -34,11 +34,13 @@ package org.hl7.fhir.r5.elementmodel; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.util.List; import java.util.Map.Entry; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.context.SimpleWorkerContext; import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; +import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; @@ -61,7 +63,7 @@ public class Tester { // new FileOutputStream("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".mm.json")), FhirFormat.JSON, OutputStyle.PRETTY); // String src = normalise(TextFile.fileToString("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".mm.json"))); // String tgt = normalise(TextFile.fileToString("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".json"))); - Element e = Manager.parse(context, new FileInputStream("C:\\work\\org.hl7.fhir\\build\\publish\\"+f), FhirFormat.XML); + Element e = Manager.parseSingle(context, new FileInputStream("C:\\work\\org.hl7.fhir\\build\\publish\\"+f), FhirFormat.XML); Manager.compose(context, e, new FileOutputStream("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".mm.ttl")), FhirFormat.TURTLE, OutputStyle.PRETTY, null); Manager.compose(context, e, new FileOutputStream("C:\\temp\\resource.xml"), FhirFormat.XML, OutputStyle.PRETTY, null); String src = TextFile.fileToString("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".mm.ttl")); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/TurtleParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/TurtleParser.java index 5a3e509b5..f5bca0176 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/TurtleParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/TurtleParser.java @@ -34,6 +34,7 @@ package org.hl7.fhir.r5.elementmodel; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -42,6 +43,7 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.elementmodel.Element.SpecialElement; +import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; import org.hl7.fhir.r5.model.StructureDefinition; @@ -74,7 +76,8 @@ public class TurtleParser extends ParserBase { super(context); } @Override - public Element parse(InputStream input) throws IOException, FHIRException { + public List parse(InputStream input) throws IOException, FHIRException { + List res = new ArrayList<>(); Turtle src = new Turtle(); if (policy == ValidationPolicy.EVERYTHING) { try { @@ -83,11 +86,18 @@ public class TurtleParser extends ParserBase { logError(-1, -1, "(document)", IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_TURTLE_, e.getMessage()), IssueSeverity.FATAL); return null; } - return parse(src); + Element e = parse(src); + if (e != null) { + res.add(new NamedElement(null, e)); + } } else { - src.parse(TextFile.streamToString(input)); - return parse(src); - } + src.parse(TextFile.streamToString(input)); + Element e = parse(src); + if (e != null) { + res.add(new NamedElement(null, e)); + } + } + return res; } private Element parse(Turtle src) throws FHIRException { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/VerticalBarParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/VerticalBarParser.java index e070b5231..27a8e3f33 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/VerticalBarParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/VerticalBarParser.java @@ -36,11 +36,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.model.StructureDefinition; @@ -450,7 +453,7 @@ public class VerticalBarParser extends ParserBase { private Delimiters delimiters = new Delimiters(); @Override - public Element parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException { + public List parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException { StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/v2/StructureDefinition/Message"); Element message = new Element("Message", new Property(context, sd.getSnapshot().getElementFirstRep(), sd)); VerticalBarParserReader reader = new VerticalBarParserReader(new BufferedInputStream(stream), charset); @@ -458,8 +461,9 @@ public class VerticalBarParser extends ParserBase { preDecode(reader); while (!reader.isFinished()) // && (getOptions().getSegmentLimit() == 0 || getOptions().getSegmentLimit() > message.getSegments().size())) readSegment(message, reader); - - return message; + List res = new ArrayList<>(); + res.add(new NamedElement(null, message)); + return res; } private void preDecode(VerticalBarParserReader reader) throws FHIRException { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java index 88f38a5f5..892080497 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java @@ -53,6 +53,7 @@ import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.conformance.ProfileUtilities; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.elementmodel.Element.SpecialElement; +import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement; import org.hl7.fhir.r5.formats.FormatUtilities; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.model.DateTimeType; @@ -106,7 +107,8 @@ public class XmlParser extends ParserBase { this.allowXsiLocation = allowXsiLocation; } - public Element parse(InputStream stream) throws FHIRFormatError, DefinitionException, FHIRException, IOException { + public List parse(InputStream stream) throws FHIRFormatError, DefinitionException, FHIRException, IOException { + List res = new ArrayList<>(); Document doc = null; try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @@ -157,10 +159,13 @@ public class XmlParser extends ParserBase { logError(0, 0, "(syntax)", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL); doc = null; } - if (doc == null) - return null; - else - return parse(doc); + if (doc != null) { + Element e = parse(doc); + if (e != null) { + res.add(new NamedElement(null, e)); + } + } + return res; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/spreadsheets/ConceptMapSpreadsheetGenerator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/spreadsheets/ConceptMapSpreadsheetGenerator.java index 64cb58ad6..47bfced53 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/spreadsheets/ConceptMapSpreadsheetGenerator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/spreadsheets/ConceptMapSpreadsheetGenerator.java @@ -32,9 +32,12 @@ public class ConceptMapSpreadsheetGenerator extends CanonicalSpreadsheetGenerato } private void addConceptMapMetadata(Sheet sheet, ConceptMap cm) { - addMetadataRow(sheet, "Source", cm.getSource().primitiveValue()); - addMetadataRow(sheet, "Target", cm.getTarget().primitiveValue()); - + if (cm.hasSource()) { + addMetadataRow(sheet, "Source", cm.getSource().primitiveValue()); + } + if (cm.hasTarget()) { + addMetadataRow(sheet, "Target", cm.getTarget().primitiveValue()); + } } private void renderGroup(ConceptMapGroupComponent grp, int i) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/spreadsheets/SpreadsheetGenerator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/spreadsheets/SpreadsheetGenerator.java index 1e62d3416..50318e3b9 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/spreadsheets/SpreadsheetGenerator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/spreadsheets/SpreadsheetGenerator.java @@ -18,9 +18,12 @@ import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.renderers.DataRenderer; +import com.microsoft.schemas.office.visio.x2012.main.ShapeSheetType; + /* Copyright (c) 2011+, HL7, Inc. All rights reserved. @@ -54,6 +57,8 @@ import org.hl7.fhir.r5.renderers.DataRenderer; public class SpreadsheetGenerator { + private static final int MAX_SENSITIVE_SHEET_NAME_LEN = 31; + protected IWorkerContext context; protected XSSFWorkbook wb = new XSSFWorkbook(); @@ -76,6 +81,9 @@ public class SpreadsheetGenerator { } protected Sheet makeSheet(String name) { + if (name.length() > MAX_SENSITIVE_SHEET_NAME_LEN - 2) { + name = name.substring(0, MAX_SENSITIVE_SHEET_NAME_LEN - 2); + } String s = name; if (sheetNames.contains(s)) { int i = 1; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java index e9be59897..470899e92 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java @@ -2,6 +2,7 @@ package org.hl7.fhir.r5.renderers.utils; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -16,6 +17,7 @@ import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; +import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.renderers.ResourceRenderer; import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; @@ -65,7 +67,11 @@ public class ElementWrappers { if (context.getParser() == null) { throw new Error("No type parser provided to renderer context"); } else { - return context.getParser().parseType(xml.toString(), type); + try { + return context.getParser().parseType(xml.toString(StandardCharsets.UTF_8), type); + } catch (Exception e) { + return new StringType("Illegal syntax: "+e.getMessage()); + } } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java index fb1c2a48a..6a906bb49 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java @@ -660,16 +660,17 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe private boolean codeInConceptFilter(CodeSystem cs, ConceptSetFilterComponent f, String code) throws FHIRException { switch (f.getOp()) { - case ISA: return codeInConceptIsAFilter(cs, f, code); - case ISNOTA: return !codeInConceptIsAFilter(cs, f, code); + case ISA: return codeInConceptIsAFilter(cs, f, code, false); + case ISNOTA: return !codeInConceptIsAFilter(cs, f, code, false); + case DESCENDENTOF: return codeInConceptIsAFilter(cs, f, code, true); default: System.out.println("todo: handle concept filters with op = "+f.getOp()); throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM__CONCEPT_FILTER_WITH_OP__, cs.getUrl(), f.getOp())); } } - private boolean codeInConceptIsAFilter(CodeSystem cs, ConceptSetFilterComponent f, String code) { - if (code.equals(f.getProperty())) { + private boolean codeInConceptIsAFilter(CodeSystem cs, ConceptSetFilterComponent f, String code, boolean rootOnly) { + if (!rootOnly && code.equals(f.getProperty())) { return true; } ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), f.getValue()); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index 18a461a21..4d1157eb6 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -5435,7 +5435,7 @@ public class FHIRPathEngine { * @throws PathEngineException * @throws DefinitionException */ - public ElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, ElementDefinition element) throws DefinitionException { + public ElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, ElementDefinition element, StructureDefinition source) throws DefinitionException { StructureDefinition sd = profile; ElementDefinition focus = null; boolean okToNotResolve = false; @@ -5494,6 +5494,7 @@ public class FHIRPathEngine { List childDefinitions = profileUtilities.getChildMap(sd, element); for (ElementDefinition t : childDefinitions) { if (t.getPath().endsWith(".extension") && t.hasSliceName()) { + System.out.println("t: "+t.getId()); StructureDefinition exsd = (t.getType() == null || t.getType().isEmpty() || t.getType().get(0).getProfile().isEmpty()) ? null : worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue()); while (exsd != null && !exsd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension")) { @@ -5508,6 +5509,9 @@ public class FHIRPathEngine { } } } + if (focus == null) { + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION, expr.toString(), targetUrl, element.getId(), sd.getUrl()); + } } else if ("ofType".equals(expr.getName())) { if (!element.hasType()) { throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_TYPE_NONE, element.getId()); @@ -5537,12 +5541,12 @@ public class FHIRPathEngine { if (okToNotResolve) { return null; } else { - throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString()); + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString(), source.getUrl(), element.getId(), profile.getUrl()); } } else if (expr.getInner() == null) { return focus; } else { - return evaluateDefinition(expr.getInner(), sd, focus); + return evaluateDefinition(expr.getInner(), sd, focus, profile); } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/IResourceValidator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/IResourceValidator.java index f5ab32be8..264a83454 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/IResourceValidator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/IResourceValidator.java @@ -281,9 +281,9 @@ public interface IResourceValidator { * in addition, you can pass one or more profiles ti validate beyond the base standard - as structure definitions or canonical URLs * @throws IOException */ - void validate(Object Context, List errors, org.hl7.fhir.r5.elementmodel.Element element) throws FHIRException; - void validate(Object Context, List errors, org.hl7.fhir.r5.elementmodel.Element element, String profile) throws FHIRException; - void validate(Object Context, List errors, org.hl7.fhir.r5.elementmodel.Element element, List profiles) throws FHIRException; + void validate(Object Context, List errors, String initialPath, org.hl7.fhir.r5.elementmodel.Element element) throws FHIRException; + void validate(Object Context, List errors, String initialPath, org.hl7.fhir.r5.elementmodel.Element element, String profile) throws FHIRException; + void validate(Object Context, List errors, String initialPath, org.hl7.fhir.r5.elementmodel.Element element, List profiles) throws FHIRException; org.hl7.fhir.r5.elementmodel.Element validate(Object Context, List errors, InputStream stream, FhirFormat format) throws FHIRException; org.hl7.fhir.r5.elementmodel.Element validate(Object Context, List errors, InputStream stream, FhirFormat format, String profile) throws FHIRException; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/PublicationHacker.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/PublicationHacker.java new file mode 100644 index 000000000..2c796b6b2 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/PublicationHacker.java @@ -0,0 +1,27 @@ +package org.hl7.fhir.r5.utils; + +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.model.ServiceRequest; +import org.hl7.fhir.r5.model.StringType; +import org.hl7.fhir.utilities.Utilities; + + +public class PublicationHacker { + + // this routine fixes up broken binding descriptions from past FHIR publications. All of them will be or are fixed in a later version, + // but fixing old versions is procedurally very difficult. Hence, these work around fixes here + + public static StringType fixBindingDescriptions(IWorkerContext context, StringType s) { + StringType res = s.copy(); + + // ServiceRequest.code + if (res.getValue().contains("LOINC is (preferred)[http://build.fhir.org/terminologies.html#preferred]")) { + res.setValue(res.getValue().replace("LOINC is (preferred)[http://build.fhir.org/terminologies.html#preferred]", "LOINC is [preferred]("+Utilities.pathURL(context.getSpecUrl(), "terminologies.html#preferred)"))); + } + if (res.getValue().contains("[here](valueset-diagnostic-requests.html)")) { + res.setValue(res.getValue().replace("[here](valueset-diagnostic-requests.html)", "here")); + } + return res; + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/FFHIRPathHostServices.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/FFHIRPathHostServices.java index cd2685f84..3748e2235 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/FFHIRPathHostServices.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/FFHIRPathHostServices.java @@ -88,7 +88,7 @@ public class FFHIRPathHostServices implements FHIRPathEngine.IEvaluationContext return noErrorValidationMessages(valerrors); } if (item instanceof Element) { - val.validate(appContext, valerrors, (Element) item, url); + val.validate(appContext, valerrors, null, (Element) item, url); return noErrorValidationMessages(valerrors); } throw new NotImplementedException("Not done yet (FFHIRPathHostServices.conformsToProfile), when item is not element or not resource"); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/CDARoundTripTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/CDARoundTripTests.java index 13408d869..b39dc90ec 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/CDARoundTripTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/CDARoundTripTests.java @@ -207,14 +207,14 @@ public class CDARoundTripTests { * @throws IOException */ public void testClinicalDocumentXmlParser() throws IOException { - Element cda = Manager.parse(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example.xml"), + Element cda = Manager.parseSingle(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example.xml"), FhirFormat.XML); assertsExample(cda); ByteArrayOutputStream baosXml = new ByteArrayOutputStream(); Manager.compose(context, cda, baosXml, FhirFormat.XML, OutputStyle.PRETTY, null); - Element cdaXmlRoundtrip = Manager.parse(context, new ByteArrayInputStream(baosXml.toString().getBytes()), FhirFormat.XML); + Element cdaXmlRoundtrip = Manager.parseSingle(context, new ByteArrayInputStream(baosXml.toString().getBytes()), FhirFormat.XML); assertsExample(cdaXmlRoundtrip); } @@ -226,14 +226,14 @@ public class CDARoundTripTests { * @throws IOException */ public void testClinicalDocumentJsonParser() throws IOException { - Element cda = Manager.parse(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example.xml"), + Element cda = Manager.parseSingle(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example.xml"), FhirFormat.XML); assertsExample(cda); ByteArrayOutputStream baosJson = new ByteArrayOutputStream(); Manager.compose(context, cda, baosJson, FhirFormat.JSON, OutputStyle.PRETTY, null); - Element cdaJsonRoundtrip = Manager.parse(context, new ByteArrayInputStream(baosJson.toString().getBytes()), + Element cdaJsonRoundtrip = Manager.parseSingle(context, new ByteArrayInputStream(baosJson.toString().getBytes()), FhirFormat.JSON); assertsExample(cdaJsonRoundtrip); @@ -245,7 +245,7 @@ public class CDARoundTripTests { * verify that umlaut like äö etc are not encoded in UTF-8 in attributes */ public void testSerializeUmlaut() throws IOException { - Element xml = Manager.parse(context, + Element xml = Manager.parseSingle(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example.xml"), FhirFormat.XML); List title = xml.getChildrenByName("title"); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java index 6bb55c3ff..7c5b733dc 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java @@ -148,7 +148,7 @@ public class NarrativeGenerationTests { Assertions.assertTrue(output.equals(target), "Output does not match expected"); if (test.isMeta()) { - org.hl7.fhir.r5.elementmodel.Element e = Manager.parse(context, TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".xml"), FhirFormat.XML); + org.hl7.fhir.r5.elementmodel.Element e = Manager.parseSingle(context, TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".xml"), FhirFormat.XML); x = RendererFactory.factory(source, rc).render(new ElementWrappers.ResourceWrapperMetaElement(rc, e)); target = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-meta.html")); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java index 4d4bcf27e..f9c5cb06e 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java @@ -66,7 +66,7 @@ public class ResourceRoundTripTests { * verify that umlaut like äö etc are not encoded in UTF-8 in attributes */ public void testSerializeUmlaut() throws IOException { - Element xml = Manager.parse(TestingUtilities.context(), TestingUtilities.loadTestResourceStream("r5", "unicode.xml"), + Element xml = Manager.parseSingle(TestingUtilities.context(), TestingUtilities.loadTestResourceStream("r5", "unicode.xml"), FhirFormat.XML); List concept = xml.getChildrenByName("concept"); assertTrue(concept!=null && concept.size()==1); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ValidationTestConvertor.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ValidationTestConvertor.java index 6380a1c88..41db20732 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ValidationTestConvertor.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ValidationTestConvertor.java @@ -30,7 +30,7 @@ public class ValidationTestConvertor { if (!t.exists()) { try { System.out.print("Process " + f.getAbsolutePath()); - Element e = Manager.parse(context, new FileInputStream(f), FhirFormat.XML); + Element e = Manager.parseSingle(context, new FileInputStream(f), FhirFormat.XML); Manager.compose(context, e, new FileOutputStream(t), FhirFormat.TURTLE, OutputStyle.PRETTY, null); System.out.println(" .... success"); } catch (Exception e) { @@ -44,7 +44,7 @@ public class ValidationTestConvertor { if (!t.exists()) { try { System.out.print("Process " + f.getAbsolutePath()); - Element e = Manager.parse(context, new FileInputStream(f), FhirFormat.JSON); + Element e = Manager.parseSingle(context, new FileInputStream(f), FhirFormat.JSON); Manager.compose(context, e, new FileOutputStream(t), FhirFormat.TURTLE, OutputStyle.PRETTY, null); System.out.println(" .... success"); } catch (Exception e) { diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/XmlParserTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/XmlParserTests.java index c7f61adda..4d9b6d97f 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/XmlParserTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/XmlParserTests.java @@ -51,7 +51,7 @@ public class XmlParserTests { * @throws IOException */ public void testXsiDeserialiserXmlParser() throws IOException { - Element cda = Manager.parse(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example-xsi.xml"), + Element cda = Manager.parseSingle(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example-xsi.xml"), FhirFormat.XML); ByteArrayOutputStream baosXml = new ByteArrayOutputStream(); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/misc/ResourceTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/misc/ResourceTest.java index c7302a300..f4233fcb2 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/misc/ResourceTest.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/misc/ResourceTest.java @@ -86,7 +86,7 @@ public class ResourceTest { } public Element testEM() throws Exception { - Element resource = Manager.parse(TestingUtilities.context(), new FileInputStream(source), isJson() ? FhirFormat.JSON : FhirFormat.XML); + Element resource = Manager.parseSingle(TestingUtilities.context(), new FileInputStream(source), isJson() ? FhirFormat.JSON : FhirFormat.XML); Manager.compose(TestingUtilities.context(), resource, new FileOutputStream(source.getAbsoluteFile()+".out.json"), FhirFormat.JSON, OutputStyle.PRETTY, null); Manager.compose(TestingUtilities.context(), resource, new FileOutputStream(source.getAbsoluteFile()+".out.json"), FhirFormat.XML, OutputStyle.PRETTY, null); return resource; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java index 9a6d306ca..5679a06e0 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java @@ -649,11 +649,13 @@ public class Utilities { StringBuilder s = new StringBuilder(); boolean d = false; for (String arg : args) { - if (!d) - d = !noString(arg); - else if (!s.toString().endsWith("/") && !arg.startsWith("/")) - s.append("/"); - s.append(arg); + if (args != null) { + if (!d) + d = !noString(arg); + else if (s.toString() != null && !s.toString().endsWith("/") && !arg.startsWith("/")) + s.append("/"); + s.append(arg); + } } return s.toString(); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index 9b06a63e2..f35a7b8f8 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -137,6 +137,7 @@ public class I18nConstants { public static final String FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST = "FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST"; public static final String FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP = "FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP"; public static final String FHIRPATH_DISCRIMINATOR_CANT_FIND = "FHIRPATH_DISCRIMINATOR_CANT_FIND"; + public static final String FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION = "FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION"; public static final String FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES = "FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES"; public static final String FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES = "FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES"; public static final String FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED = "FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED"; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JSONUtil.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JSONUtil.java index 2882dabf5..c2ca45f2d 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JSONUtil.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JSONUtil.java @@ -45,6 +45,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; public class JSONUtil { @@ -139,4 +140,27 @@ public class JSONUtil { return (JsonObject) new com.google.gson.JsonParser().parse(TextFile.streamToString(c.getInputStream())); } + public static String type(JsonElement e) { + if (e == null) { + return "(null)"; + } + if (e.isJsonObject()) { + return "Object"; + } + if (e.isJsonArray()) { + return "Array"; + } + if (e.isJsonNull()) { + return "Null"; + } + JsonPrimitive p = (JsonPrimitive) e; + if (p.isBoolean()) { + return "Boolean"; + } + if (p.isNumber()) { + return "Number"; + } + return "String"; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageHacker.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageHacker.java index ae63fbc85..94def3aa8 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageHacker.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageHacker.java @@ -138,6 +138,7 @@ public class PackageHacker { case "file://C:\\GitHub\\hl7.fhir.uv.mhealth-framework#0.1.0\\output": return "http://hl7.org/fhir/uv/mhealth-framework/2020May"; case "file://C:\\GitHub\\hl7.fhir.uv.security-label-ds4p#0.1.0\\output": return "http://hl7.org/fhir/uv/security-label-ds4p/2020May"; case "file://C:\\GitHub\\hl7.fhir.uv.shorthand#0.12.0\\output": return "http://hl7.org/fhir/uv/shorthand/2020May"; + case "http://build.fhir.org/branches/R4B//": return "http://hl7.org/fhir/2021Mar"; } // https://github.com/HL7/fhir-ig-publisher/issues/295 diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 19c6d4a89..11b60a0dd 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -224,7 +224,7 @@ Validation_VAL_Profile_NoSnapshot = StructureDefinition has no snapshot - valida Validation_VAL_Profile_NoType = The type of element {0} is not known, which is illegal. Valid types at this point are {1} Validation_VAL_Profile_NotAllowed = This element is not allowed by the profile {0} Validation_VAL_Profile_NotSlice = This element does not match any known slice {0} and slicing is CLOSED: {1} -Validation_VAL_Profile_OutOfOrder = As specified by profile {0}, Element ''{1}'' is out of order +Validation_VAL_Profile_OutOfOrder = As specified by profile {0}, Element ''{1}'' is out of order (found after {2}) Validation_VAL_Profile_SliceOrder = As specified by profile {0}, Element ''{1}'' is out of order in ordered slice Validation_VAL_Profile_Unknown = Profile reference ''{0}'' has not been checked because it is unknown VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY = Profile reference ''{0}'' has not been checked because it is unknown, and the validator is set to not fetch unknown profiles @@ -569,7 +569,8 @@ FHIRPATH_DISCRIMINATOR_NO_CODE = illegal use of ofType() in discriminator - Type FHIRPATH_DISCRIMINATOR_BAD_NAME = illegal function name {0}() in discriminator FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP = illegal expression syntax in discriminator (group) FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST = illegal expression syntax in discriminator (const) -FHIRPATH_DISCRIMINATOR_CANT_FIND = Unable to resolve discriminator in definitions: {0} +FHIRPATH_DISCRIMINATOR_CANT_FIND = Unable to resolve discriminator in definitions: {0} in profile {1} on element {2}, looking in profile {3} +FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION = Unable to resolve discriminator {0} on {2} found in the definitions because the extension {1} wasn''t found in the profile {3} FHIRPATH_DISCRIMINATOR_NOTYPE = Error in discriminator at {0}: no children, no type FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES = Error in discriminator at {0}: no children, multiple types FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES = Error in discriminator at {0}: no children, multiple type profiles diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java index f918c648e..bcf4283f1 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java @@ -189,7 +189,7 @@ public class IgLoader { return readZip(new FileInputStream(src)); if (src.endsWith("igpack.zip")) return readZip(new FileInputStream(src)); - Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), src); + Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), TextFile.fileToBytes(f), src, true); if (fmt != null) { Map res = new HashMap(); res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), TextFile.fileToBytesNCS(src)); @@ -321,7 +321,7 @@ public class IgLoader { return readZip(new FileInputStream(src)); if (src.endsWith("igpack.zip")) return readZip(new FileInputStream(src)); - Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), src); + Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), TextFile.fileToBytes(f), src, true); if (fmt != null) { Map res = new HashMap(); res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), TextFile.fileToBytesNCS(src)); @@ -470,7 +470,7 @@ public class IgLoader { else cnt = TextFile.streamToBytes(stream); - Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), cnt, src); + Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), cnt, src, true); if (fmt != null) { Map res = new HashMap(); res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), cnt); @@ -569,7 +569,7 @@ public class IgLoader { if (ff.isDirectory() && recursive) { res.putAll(scanDirectory(ff, true)); } else if (!ff.isDirectory() && !isIgnoreFile(ff)) { - Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), ff.getAbsolutePath()); + Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), TextFile.fileToBytes(ff), ff.getAbsolutePath(), true); if (fmt != null) { res.put(Utilities.changeFileExt(ff.getName(), "." + fmt.getExtension()), TextFile.fileToBytes(ff.getAbsolutePath())); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ResourceChecker.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ResourceChecker.java index 190beaf19..ab0c772f4 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ResourceChecker.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ResourceChecker.java @@ -2,16 +2,75 @@ package org.hl7.fhir.validation; import org.hl7.fhir.r5.context.SimpleWorkerContext; import org.hl7.fhir.r5.elementmodel.Manager; +import org.hl7.fhir.r5.elementmodel.SHCParser; +import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; +import org.hl7.fhir.r5.elementmodel.SHCParser.JWT; import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.json.JSONUtil; +import org.hl7.fhir.utilities.json.JsonTrackingParser; + +import com.google.gson.JsonObject; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; public class ResourceChecker { - protected static Manager.FhirFormat checkIsResource(SimpleWorkerContext context, boolean debug, byte[] cnt, String filename) { + +// protected static Manager.FhirFormat checkIsResource(SimpleWorkerContext context, boolean debug, String path) throws IOException { +// +// if (Utilities.existsInList(ext, "json")) +// return Manager.FhirFormat.JSON; +// if (Utilities.existsInList(ext, "map")) +// return Manager.FhirFormat.TEXT; +// if (Utilities.existsInList(ext, "txt")) +// return Manager.FhirFormat.TEXT; +// if (Utilities.existsInList(ext, "jwt", "jws")) +// return Manager.FhirFormat.SHC; +// +// return checkIsResource(context, debug, TextFile.fileToBytes(path), path); +// } + public static Manager.FhirFormat checkIsResource(SimpleWorkerContext context, boolean debug, byte[] cnt, String filename, boolean guessFromExtension) { System.out.println(" ..Detect format for " + filename); + if (guessFromExtension) { + String ext = Utilities.getFileExtension(filename); + if (Utilities.existsInList(ext, "xml")) { + return FhirFormat.XML; + } + if (Utilities.existsInList(ext, "ttl")) { + return FhirFormat.TURTLE; + } + if (Utilities.existsInList(ext, "map")) { + return Manager.FhirFormat.TEXT; + } + if (Utilities.existsInList(ext, "jwt", "jws")) { + return Manager.FhirFormat.SHC; + } + if (Utilities.existsInList(ext, "json")) { + // no, we have to look inside, and decide. + try { + JsonObject json = JsonTrackingParser.parseJson(cnt); + if (json.has("verifiableCredential")) { + return FhirFormat.SHC; + } + } catch (Exception e) { + } + return FhirFormat.JSON; + } + if (Utilities.existsInList(ext, "txt")) { + try { + String src = TextFile.bytesToString(cnt); + if (src.startsWith("shc:/")) { + return FhirFormat.SHC; + } + } catch (Exception e) { + } + return Manager.FhirFormat.TEXT; + } + } + try { Manager.parse(context, new ByteArrayInputStream(cnt), Manager.FhirFormat.JSON); return Manager.FhirFormat.JSON; @@ -36,6 +95,17 @@ public class ResourceChecker { System.out.println("Not Turtle: " + e.getMessage()); } } + try { + String s = new String(cnt, StandardCharsets.UTF_8); + if (s.startsWith("shc:/")) + s = SHCParser.decodeQRCode(s); + JWT jwt = SHCParser.decodeJWT(s); + return Manager.FhirFormat.SHC; + } catch (Exception e) { + if (debug) { + System.out.println("Not a smart health card: " + e.getMessage()); + } + } try { new StructureMapUtilities(context, null, null).parse(TextFile.bytesToString(cnt), null); return Manager.FhirFormat.TEXT; @@ -49,19 +119,5 @@ public class ResourceChecker { return null; } - protected static Manager.FhirFormat checkIsResource(SimpleWorkerContext context, boolean debug, String path) throws IOException { - String ext = Utilities.getFileExtension(path); - if (Utilities.existsInList(ext, "xml")) - return Manager.FhirFormat.XML; - if (Utilities.existsInList(ext, "json")) - return Manager.FhirFormat.JSON; - if (Utilities.existsInList(ext, "ttl")) - return Manager.FhirFormat.TURTLE; - if (Utilities.existsInList(ext, "map")) - return Manager.FhirFormat.TEXT; - if (Utilities.existsInList(ext, "txt")) - return Manager.FhirFormat.TEXT; - - return checkIsResource(context, debug, TextFile.fileToBytes(path), path); - } + } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 8c4cbc52f..78ba56a04 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -21,6 +21,7 @@ import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.elementmodel.Manager; import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; import org.hl7.fhir.r5.elementmodel.ObjectConverter; +import org.hl7.fhir.r5.elementmodel.SHCParser; import org.hl7.fhir.r5.formats.FormatUtilities; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.formats.JsonParser; @@ -293,7 +294,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst // testing entry point public OperationOutcome validate(FhirFormat format, InputStream stream, List profiles) throws FHIRException, IOException, EOperationOutcome { List messages = new ArrayList(); - InstanceValidator validator = getValidator(); + InstanceValidator validator = getValidator(format); validator.validate(null, messages, stream, format, asSdList(profiles)); return ValidatorUtils.messagesToOutcome(messages, context, fhirPathEngine); } @@ -350,7 +351,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst } public OperationOutcome validate(byte[] source, FhirFormat cntType, List profiles, List messages) throws FHIRException, IOException, EOperationOutcome { - InstanceValidator validator = getValidator(); + InstanceValidator validator = getValidator(cntType); validator.validate(null, messages, new ByteArrayInputStream(source), cntType, asSdList(profiles)); return ValidatorUtils.messagesToOutcome(messages, context, fhirPathEngine); @@ -361,7 +362,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (doNative) { SchemaValidator.validateSchema(location, cntType, messages); } - InstanceValidator validator = getValidator(); + InstanceValidator validator = getValidator(cntType); validator.validate(null, messages, new ByteArrayInputStream(source), cntType, asSdList(profiles)); if (showTimes) { System.out.println(location + ": " + validator.reportTimes()); @@ -377,7 +378,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (doNative) { SchemaValidator.validateSchema(location, cntType, messages); } - InstanceValidator validator = getValidator(); + InstanceValidator validator = getValidator(cntType); validator.setResourceIdRule(resourceIdRule); validator.setBestPracticeWarningLevel(bpWarnings); validator.setCheckDisplay(displayOption); @@ -393,7 +394,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst public org.hl7.fhir.r5.elementmodel.Element transform(byte[] source, FhirFormat cntType, String mapUri) throws FHIRException, IOException { List outputs = new ArrayList<>(); StructureMapUtilities scu = new StructureMapUtilities(context, new TransformSupportServices(outputs, mapLog, context)); - org.hl7.fhir.r5.elementmodel.Element src = Manager.parse(context, new ByteArrayInputStream(source), cntType); + org.hl7.fhir.r5.elementmodel.Element src = Manager.parseSingle(context, new ByteArrayInputStream(source), cntType); StructureMap map = context.getTransform(mapUri); if (map == null) throw new Error("Unable to find map " + mapUri + " (Known Maps = " + context.listMapUrls() + ")"); org.hl7.fhir.r5.elementmodel.Element resource = getTargetResourceFromStructureMap(map); @@ -448,14 +449,14 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst public void convert(String source, String output) throws FHIRException, IOException { Content cnt = igLoader.loadContent(source, "validate", false); - Element e = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType); + Element e = Manager.parseSingle(context, new ByteArrayInputStream(cnt.focus), cnt.cntType); Manager.compose(context, e, new FileOutputStream(output), (output.endsWith(".json") ? FhirFormat.JSON : FhirFormat.XML), OutputStyle.PRETTY, null); } public String evaluateFhirPath(String source, String expression) throws FHIRException, IOException { Content cnt = igLoader.loadContent(source, "validate", false); - FHIRPathEngine fpe = this.getValidator().getFHIRPathEngine(); - Element e = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType); + FHIRPathEngine fpe = this.getValidator(null).getFHIRPathEngine(); + Element e = Manager.parseSingle(context, new ByteArrayInputStream(cnt.focus), cnt.cntType); ExpressionNode exp = fpe.parse(expression); return fpe.evaluateToString(new ValidatorHostContext(context, e), e, e, e, exp); } @@ -490,7 +491,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst context.dropResource(type, id); } - public InstanceValidator getValidator() { + public InstanceValidator getValidator(FhirFormat format) throws FHIRException, IOException { InstanceValidator validator = new InstanceValidator(context, null, null); validator.setHintAboutNonMustSupport(hintAboutNonMustSupport); validator.setAnyExtensionsAllowed(anyExtensionsAllowed); @@ -512,6 +513,10 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst validator.getBundleValidationRules().addAll(bundleValidationRules); validator.getValidationControl().putAll(validationControl); validator.setQuestionnaireMode(questionnaireMode); + if (format == FhirFormat.SHC) { + igLoader.loadIg(getIgs(), getBinaries(), SHCParser.CURRENT_PACKAGE, true); + } + return validator; } @@ -617,7 +622,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst public byte[] transformVersion(String source, String targetVer, FhirFormat format, Boolean canDoNative) throws FHIRException, IOException, Exception { Content cnt = igLoader.loadContent(source, "validate", false); - org.hl7.fhir.r5.elementmodel.Element src = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType); + org.hl7.fhir.r5.elementmodel.Element src = Manager.parseSingle(context, new ByteArrayInputStream(cnt.focus), cnt.cntType); // if the src has a url, we try to use the java code if ((canDoNative == null && src.hasChild("url")) || (canDoNative != null && canDoNative)) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java index b00b20903..adcd3bf30 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java @@ -235,7 +235,7 @@ public class ValidatorCli { } System.out.println("Validating"); if (cliContext.getMode() == EngineMode.SCAN) { - Scanner validationScanner = new Scanner(validator.getContext(), validator.getValidator(), validator.getIgLoader(), validator.getFhirPathEngine()); + Scanner validationScanner = new Scanner(validator.getContext(), validator.getValidator(null), validator.getIgLoader(), validator.getFhirPathEngine()); validationScanner.validateScan(cliContext.getOutput(), cliContext.getSources()); } else { validationService.validateSources(cliContext, validator); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index 363155ca0..45d71ff96 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -67,6 +67,7 @@ import org.hl7.fhir.r5.elementmodel.Manager; import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; import org.hl7.fhir.r5.elementmodel.ObjectConverter; import org.hl7.fhir.r5.elementmodel.ParserBase; +import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement; import org.hl7.fhir.r5.elementmodel.ParserBase.ValidationPolicy; import org.hl7.fhir.r5.elementmodel.XmlParser; import org.hl7.fhir.r5.formats.FormatUtilities; @@ -296,13 +297,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat try { Element e = new ObjectConverter(context).convert((Resource) item); setParents(e); - self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, e, validationLanguage)); + self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); } catch (IOException e1) { throw new FHIRException(e1); } } else if (item instanceof Element) { Element e = (Element) item; - self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, e, validationLanguage)); + self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); } else throw new NotImplementedException(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCONFORMSTOPROFILE_WHEN_ITEM_IS_NOT_AN_ELEMENT)); boolean ok = true; @@ -566,16 +567,28 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ((XmlParser) parser).setAllowXsiLocation(allowXsiLocation); parser.setupValidation(ValidationPolicy.EVERYTHING, errors); long t = System.nanoTime(); - Element e; + List list = null; try { - e = parser.parse(stream); + list = parser.parse(stream); } catch (IOException e1) { throw new FHIRException(e1); } timeTracker.load(t); - if (e != null) - validate(appContext, errors, e, profiles); - return e; + if (list != null && !list.isEmpty()) { + String url = parser.getImpliedProfile(); + if (url != null) { + StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); + if (sd == null) { + rule(errors, IssueType.NOTFOUND, "Payload", false, "Implied profile "+url+" not known to validator"); + } else { + profiles.add(sd); + } + } + for (NamedElement ne : list) { + validate(appContext, errors, ne.getName(), ne.getElement(), profiles); + } + } + return (list == null || list.isEmpty()) ? null : list.get(0).getElement(); // todo: this is broken, but fixing it really complicates things elsewhere, so we do this for now } @Override @@ -602,7 +615,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat throw new FHIRException(e1); } timeTracker.load(t); - validate(appContext, errors, e, profiles); + validate(appContext, errors, null, e, profiles); return e; } @@ -633,7 +646,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } timeTracker.load(t); if (e != null) { - validate(appContext, errors, e, profiles); + validate(appContext, errors, null, e, profiles); } return e; } @@ -665,7 +678,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } timeTracker.load(t); if (e != null) - validate(appContext, errors, e, profiles); + validate(appContext, errors, null, e, profiles); return e; } @@ -691,26 +704,26 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat Element e = parser.parse(object); timeTracker.load(t); if (e != null) - validate(appContext, errors, e, profiles); + validate(appContext, errors, null, e, profiles); return e; } @Override - public void validate(Object appContext, List errors, Element element) throws FHIRException { - validate(appContext, errors, element, new ArrayList<>()); + public void validate(Object appContext, List errors, String initialPath, Element element) throws FHIRException { + validate(appContext, errors, initialPath, element, new ArrayList<>()); } @Override - public void validate(Object appContext, List errors, Element element, String profile) throws FHIRException { + public void validate(Object appContext, List errors, String initialPath, Element element, String profile) throws FHIRException { ArrayList profiles = new ArrayList<>(); if (profile != null) { profiles.add(getSpecifiedProfile(profile)); } - validate(appContext, errors, element, profiles); + validate(appContext, errors, initialPath, element, profiles); } @Override - public void validate(Object appContext, List errors, Element element, List profiles) throws FHIRException { + public void validate(Object appContext, List errors, String path, Element element, List profiles) throws FHIRException { // this is the main entry point; all the other public entry points end up here coming here... // so the first thing to do is to clear the internal state fetchCache.clear(); @@ -724,14 +737,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat long t = System.nanoTime(); if (profiles == null || profiles.isEmpty()) { - validateResource(new ValidatorHostContext(appContext, element), errors, element, element, null, resourceIdRule, new NodeStack(context, element, validationLanguage).resetIds()); + validateResource(new ValidatorHostContext(appContext, element), errors, element, element, null, resourceIdRule, new NodeStack(context, path, element, validationLanguage).resetIds()); } else { for (StructureDefinition defn : profiles) { - validateResource(new ValidatorHostContext(appContext, element), errors, element, element, defn, resourceIdRule, new NodeStack(context, element, validationLanguage).resetIds()); + validateResource(new ValidatorHostContext(appContext, element), errors, element, element, defn, resourceIdRule, new NodeStack(context, path, element, validationLanguage).resetIds()); } } if (hintAboutNonMustSupport) { - checkElementUsage(errors, element, new NodeStack(context, element, validationLanguage)); + checkElementUsage(errors, element, new NodeStack(context, path, element, validationLanguage)); } errors.removeAll(messagesToRemove); timeTracker.overall(t); @@ -3049,7 +3062,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return context; } - private List getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile, boolean removeResolve) throws FHIRException { + private List getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile, boolean removeResolve, StructureDefinition srcProfile) throws FHIRException { List elements = new ArrayList(); if ("value".equals(discriminator) && element.hasFixed()) { elements.add(element); @@ -3075,7 +3088,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR_BAD_PATH, e.getMessage(), fp), e); } long t2 = System.nanoTime(); - ed = fpe.evaluateDefinition(expr, profile, element); + ed = fpe.evaluateDefinition(expr, profile, element, srcProfile); timeTracker.sd(t2); if (ed != null) elements.add(ed); @@ -3100,7 +3113,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } expr = fpe.parse(fp); t2 = System.nanoTime(); - ed = fpe.evaluateDefinition(expr, profile, element); + ed = fpe.evaluateDefinition(expr, profile, element, srcProfile); timeTracker.sd(t2); if (ed != null) elements.add(ed); @@ -3387,7 +3400,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat rr.setResource(res.getMatch()); rr.setFocus(res.getMatch()); rr.setExternal(false); - rr.setStack(new NodeStack(context, hostContext, validationLanguage).push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(), + rr.setStack(new NodeStack(context, null, hostContext, validationLanguage).push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(), res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1, res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition())); rr.getStack().qualifyPath(".ofType("+rr.getResource().fhirType()+")"); @@ -3550,13 +3563,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat * @param ed - the slice for which to test membership * @param errors * @param stack + * @param srcProfile * @return * @throws DefinitionException * @throws DefinitionException * @throws IOException * @throws FHIRException */ - private boolean sliceMatches(ValidatorHostContext hostContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List errors, List sliceInfo, NodeStack stack) throws DefinitionException, FHIRException { + private boolean sliceMatches(ValidatorHostContext hostContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List errors, List sliceInfo, NodeStack stack, StructureDefinition srcProfile) throws DefinitionException, FHIRException { if (!slicer.getSlicing().hasDiscriminator()) return false; // cannot validate in this case @@ -3571,7 +3585,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String discriminator = s.getPath(); discriminators.add(discriminator); - List criteriaElements = getCriteriaForDiscriminator(path, ed, discriminator, profile, s.getType() == DiscriminatorType.PROFILE); + List criteriaElements = getCriteriaForDiscriminator(path, ed, discriminator, profile, s.getType() == DiscriminatorType.PROFILE, srcProfile); boolean found = false; for (ElementDefinition criteriaElement : criteriaElements) { found = true; @@ -4873,6 +4887,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } int last = -1; + ElementInfo lastei = null; int lastSlice = -1; for (ElementInfo ei : children) { String sliceInfo = ""; @@ -4911,13 +4926,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (!ToolingExtensions.readBoolExtension(profile, "http://hl7.org/fhir/StructureDefinition/structuredefinition-xml-no-order")) { boolean ok = (ei.definition == null) || (ei.index >= last) || isXmlAttr; - rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), ok, I18nConstants.VALIDATION_VAL_PROFILE_OUTOFORDER, profile.getUrl(), ei.getName()); + rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), ok, I18nConstants.VALIDATION_VAL_PROFILE_OUTOFORDER, profile.getUrl(), ei.getName(), lastei == null ? "(null)" : lastei.getName()); } if (ei.slice != null && ei.index == last && ei.slice.getSlicing().getOrdered()) { rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), (ei.definition == null) || (ei.sliceindex >= lastSlice) || isXmlAttr, I18nConstants.VALIDATION_VAL_PROFILE_SLICEORDER, profile.getUrl(), ei.getName()); } if (ei.definition == null || !isXmlAttr) { last = ei.index; + lastei = ei; } if (ei.slice != null) { lastSlice = ei.sliceindex; @@ -4950,7 +4966,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else { if (nameMatches(ei.getName(), tail(ed.getPath()))) try { - match = sliceMatches(hostContext, ei.getElement(), ei.getPath(), slicer, ed, profile, errors, sliceInfo, stack); + match = sliceMatches(hostContext, ei.getElement(), ei.getPath(), slicer, ed, profile, errors, sliceInfo, stack, profile); if (match) { ei.slice = slicer; diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/NodeStack.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/NodeStack.java index d1f7fe45d..e753da6d7 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/NodeStack.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/NodeStack.java @@ -30,11 +30,11 @@ public class NodeStack { this.context = context; } - public NodeStack(IWorkerContext context, Element element, String validationLanguage) { + public NodeStack(IWorkerContext context, String initialPath, Element element, String validationLanguage) { this.context = context; ids = new HashMap<>(); this.element = element; - literalPath = element.getPath(); + literalPath = (initialPath == null ? "" : initialPath+".") + element.getPath(); workingLang = validationLanguage; if (!element.getName().equals(element.fhirType())) { logicalPaths = new ArrayList<>(); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java index 30ee70d92..5dbdb0028 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java @@ -1,11 +1,13 @@ package org.hl7.fhir.validation.tests; +import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -31,6 +33,7 @@ import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.elementmodel.Manager; import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; import org.hl7.fhir.r5.elementmodel.ObjectConverter; +import org.hl7.fhir.r5.elementmodel.SHCParser; import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.formats.XmlParser; import org.hl7.fhir.r5.model.Base; @@ -162,8 +165,11 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe if (content.has("use-test") && !content.get("use-test").getAsBoolean()) return; - String testCaseContent = TestingUtilities.loadTestResource("validator", JSONUtil.str(content, "file")); - InstanceValidator val = vCurr.getValidator(); + byte[] testCaseContent = TestingUtilities.loadTestResource("validator", JSONUtil.str(content, "file")).getBytes(StandardCharsets.UTF_8); + // load and process content + FhirFormat fmt = determineFormat(content, testCaseContent); + + InstanceValidator val = vCurr.getValidator(fmt); val.setWantCheckSnapshotUnchanged(true); val.getContext().setClientRetryCount(4); val.setDebug(false); @@ -236,13 +242,11 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe if (content.has("security-checks")) { val.setSecurityChecks(content.get("security-checks").getAsBoolean()); } + if (content.has("logical")==false) { val.setAssumeValidRestReferences(content.has("assumeValidRestReferences") ? content.get("assumeValidRestReferences").getAsBoolean() : false); System.out.println(String.format("Start Validating (%d to set up)", (System.nanoTime() - setup) / 1000000)); - if (JSONUtil.str(content, "file").endsWith(".json")) - val.validate(null, errors, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON); - else - val.validate(null, errors, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.XML); + val.validate(null, errors, new ByteArrayInputStream(testCaseContent), fmt); System.out.println(val.reportTimes()); checkOutcomes(errors, content, null, name); } @@ -281,10 +285,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe } val.setAssumeValidRestReferences(profile.has("assumeValidRestReferences") ? profile.get("assumeValidRestReferences").getAsBoolean() : false); List errorsProfile = new ArrayList(); - if (JSONUtil.str(content, "file").endsWith(".json")) - val.validate(null, errorsProfile, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON, asSdList(sd)); - else - val.validate(null, errorsProfile, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.XML, asSdList(sd)); + val.validate(null, errorsProfile, new ByteArrayInputStream(testCaseContent), fmt, asSdList(sd)); System.out.println(val.reportTimes()); checkOutcomes(errorsProfile, profile, filename, name); } @@ -309,7 +310,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe } } List errorsLogical = new ArrayList(); - Element le = val.validate(null, errorsLogical, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), (name.endsWith(".json")) ? FhirFormat.JSON : FhirFormat.XML); + Element le = val.validate(null, errorsLogical, new ByteArrayInputStream(testCaseContent), fmt); if (logical.has("expressions")) { FHIRPathEngine fp = new FHIRPathEngine(val.getContext()); for (JsonElement e : logical.getAsJsonArray("expressions")) { @@ -321,6 +322,11 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe } } + private FhirFormat determineFormat(JsonObject config, byte[] cnt) throws IOException { + String name = JSONUtil.str(config, "file"); + return org.hl7.fhir.validation.ResourceChecker.checkIsResource(vCurr.getContext(), true, cnt, name, !JSONUtil.bool(config, "guess-format")); + } + private List asSdList(StructureDefinition sd) { List res = new ArrayList(); res.add(sd); @@ -482,16 +488,16 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe if (url.equals("Patient/test")) { res = new ObjectConverter(TestingUtilities.context(version)).convert(new Patient()); } else if (TestingUtilities.findTestResource("validator", url.replace("/", "-").toLowerCase() + ".json")) { - res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.JSON).parse(TestingUtilities.loadTestResourceStream("validator", url.replace("/", "-").toLowerCase() + ".json")); + res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.JSON).parseSingle(TestingUtilities.loadTestResourceStream("validator", url.replace("/", "-").toLowerCase() + ".json")); } else if (TestingUtilities.findTestResource("validator", url.replace("/", "-").toLowerCase() + ".xml")) { - res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.XML).parse(TestingUtilities.loadTestResourceStream("validator", url.replace("/", "-").toLowerCase() + ".xml")); + res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.XML).parseSingle(TestingUtilities.loadTestResourceStream("validator", url.replace("/", "-").toLowerCase() + ".xml")); } if (res == null && url.contains("/")) { String tail = url.substring(url.indexOf("/") + 1); if (TestingUtilities.findTestResource("validator", tail.replace("/", "-").toLowerCase() + ".json")) { - res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.JSON).parse(TestingUtilities.loadTestResourceStream("validator", tail.replace("/", "-").toLowerCase() + ".json")); + res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.JSON).parseSingle(TestingUtilities.loadTestResourceStream("validator", tail.replace("/", "-").toLowerCase() + ".json")); } else if (TestingUtilities.findTestResource("validator", tail.replace("/", "-").toLowerCase() + ".xml")) { - res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.XML).parse(TestingUtilities.loadTestResourceStream("validator", tail.replace("/", "-").toLowerCase() + ".xml")); + res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.XML).parseSingle(TestingUtilities.loadTestResourceStream("validator", tail.replace("/", "-").toLowerCase() + ".xml")); } } return res;