From 59b0f032cbef4407316409a9db0723fc70321f5d Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 31 Jul 2020 12:48:54 +1000 Subject: [PATCH 1/9] fix for NPE in FHIRPath parser --- .../src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java index 9e4ce7ce1..e08acbefb 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java @@ -71,7 +71,7 @@ public class FHIRLexer { private String name; public FHIRLexer(String source, String name) throws FHIRLexerException { - this.source = source; + this.source = source == null ? "" : source; this.name = name == null ? "??" : name; currentLocation = new SourceLocation(1, 1); next(); From ebead8fd7c4ae9316694b90e0884d09227703ea2 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 31 Jul 2020 12:51:43 +1000 Subject: [PATCH 2/9] fix bugs in comparisons --- .../org/hl7/fhir/r5/comparison/CodeSystemComparer.java | 2 +- .../org/hl7/fhir/r5/comparison/ComparisonRenderer.java | 7 ++++++- .../java/org/hl7/fhir/r5/comparison/ValueSetComparer.java | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java index fd0810f48..d4ab2f2b3 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java @@ -154,7 +154,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer { private void compareConcepts(List left, List right, StructuralMatch combined, - List union, List intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path) { + List union, List intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path) { List matchR = new ArrayList<>(); for (ConceptDefinitionComponent l : left) { ConceptDefinitionComponent r = findInList(right, l); 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 075455058..b077b44b7 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 @@ -84,7 +84,12 @@ public class ComparisonRenderer implements IEvaluationContext { first = false; b.append(""+name+"\r\n"); } - renderComparison(id, comp); + try { + renderComparison(id, comp); + } catch (Exception e) { + System.out.println("Exception rendering "+id+": "+e.getMessage()); + e.printStackTrace(); + } b.append(comp.toTable()); //"
  • "+Utilities.escapeXml(comp.summary())+"
  • \r\n" } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java index 1bf21ebbc..674db72ab 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java @@ -210,7 +210,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { private int countMatchesBySystem(List list, ConceptSetComponent item) { int c = 0; for (ConceptSetComponent t : list) { - if (t.getSystem().equals(item.getSystem())) { + if (t.hasSystem() && t.getSystem().equals(item.getSystem())) { c++; } } From 1888925293f3fac88d4f9e8a2f9b60323b9d23e4 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 31 Jul 2020 12:52:02 +1000 Subject: [PATCH 3/9] upgrade version of pubpack --- .../org/hl7/fhir/utilities/tests/CachingPackageClientTests.java | 2 +- .../java/org/hl7/fhir/utilities/tests/PackageCacheTests.java | 2 +- .../java/org/hl7/fhir/comparison/tests/ComparisonTests.java | 2 +- .../org/hl7/fhir/validation/tests/ProfileComparisonTests.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/CachingPackageClientTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/CachingPackageClientTests.java index 08cf87701..2e897e910 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/CachingPackageClientTests.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/CachingPackageClientTests.java @@ -58,7 +58,7 @@ public class CachingPackageClientTests { Assertions.assertTrue(client.exists("hl7.fhir.r4.core", "4.0.1")); Assertions.assertTrue(!client.exists("hl7.fhir.r4.core", "1.0.2")); Assertions.assertTrue(!client.exists("hl7.fhir.nothing", "1.0.1")); - Assertions.assertTrue(client.exists("hl7.fhir.pubpack", "0.0.6")); + Assertions.assertTrue(client.exists("hl7.fhir.pubpack", "0.0.7")); } @Test diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java index cf5974a6c..4f42035eb 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java @@ -22,7 +22,7 @@ public class PackageCacheTests { System.out.println("remaining packages: "+list.toString()); } Assertions.assertTrue(list.isEmpty(), "List should be true but is "+list.toString()); - NpmPackage npm = cache.loadPackage("hl7.fhir.pubpack", "0.0.3"); + NpmPackage npm = cache.loadPackage("hl7.fhir.pubpack", "0.0.7"); npm.loadAllFiles(); Assertions.assertNotNull(npm); File dir = new File(Utilities.path("[tmp]", "cache")); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java index d4467f175..130195e89 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java @@ -107,7 +107,7 @@ public class ComparisonTests { System.out.println("---- Set up Output ----------------------------------------------------------"); Utilities.createDirectory(Utilities.path("[tmp]", "comparison")); FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); - NpmPackage npm = pcm.loadPackage("hl7.fhir.pubpack", "0.0.6"); + NpmPackage npm = pcm.loadPackage("hl7.fhir.pubpack", "0.0.7"); for (String f : npm.list("other")) { TextFile.streamToFile(npm.load("other", f), Utilities.path("[tmp]", "comparison", f)); } diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ProfileComparisonTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ProfileComparisonTests.java index b52f3fb8d..39cbc6c25 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ProfileComparisonTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ProfileComparisonTests.java @@ -13,7 +13,7 @@ public class ProfileComparisonTests { // ValidationEngine ve = new ValidationEngine("hl7.fhir.r3.core#3.0.2", DEF_TX, null, FhirPublication.STU3, "3.0.2"); // ve.loadIg("hl7.fhir.us.core#1.0.1", false); // ve.loadIg("hl7.fhir.au.base#current", false); -// ve.getContext().loadFromPackage(new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION).loadPackage("hl7.fhir.pubpack", "0.0.4"), new R5ToR5Loader(new String[] {"Binary"}), "Binary"); +// ve.getContext().loadFromPackage(new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION).loadPackage("hl7.fhir.pubpack", "0.0.7"), new R5ToR5Loader(new String[] {"Binary"}), "Binary"); // // // String left = "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"; From 477bf66108182b55907d673cb31e924d30b9aea5 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sat, 1 Aug 2020 08:55:46 +1000 Subject: [PATCH 4/9] Fix broken links in Bundle rendering --- .../java/org/hl7/fhir/r5/renderers/RendererFactory.java | 9 +++++++-- .../java/org/hl7/fhir/r5/renderers/ResourceRenderer.java | 6 ++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java index 7becef9a2..9fb41cc12 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java @@ -4,6 +4,7 @@ import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; public class RendererFactory { @@ -87,7 +88,7 @@ public class RendererFactory { } - public static ResourceRenderer factory(ResourceWrapper resource, RenderingContext context) { + public static ResourceRenderer factory(ResourceWrapper resource, RenderingContext context, ResourceContext resourceContext) { if (context.getTemplateProvider() != null) { String liquidTemplate = context.getTemplateProvider().findTemplate(context, resource.getName()); if (liquidTemplate != null) { @@ -102,7 +103,11 @@ public class RendererFactory { return new DiagnosticReportRenderer(context); } - return new ProfileDrivenRenderer(context); + return new ProfileDrivenRenderer(context, resourceContext); + } + + public static ResourceRenderer factory(ResourceWrapper rw, RenderingContext lrc) { + return factory(rw, lrc, null); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java index d494dd9fc..a855e71da 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java @@ -7,6 +7,7 @@ import org.apache.commons.lang3.NotImplementedException; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; @@ -208,8 +209,9 @@ public abstract class ResourceRenderer extends DataRenderer { org.hl7.fhir.r5.elementmodel.Element bundleElement = rcontext.resolveElement(url); if (bundleElement != null) { String bundleUrl = null; - if (bundleElement.getNamedChild("resource").getChildValue("id") != null) { - bundleUrl = "#" + bundleElement.fhirType().toLowerCase() + "_" + bundleElement.getNamedChild("resource").getChildValue("id"); + Element br = bundleElement.getNamedChild("resource"); + if (br.getChildValue("id") != null) { + bundleUrl = "#" + br.fhirType().toLowerCase() + "_" + br.getChildValue("id"); } else { bundleUrl = "#" +fullUrlToAnchor(bundleElement.getChildValue("fullUrl")); } From 137d04f68d27875dd2d97d951cb4170740d15c00 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 3 Aug 2020 17:30:37 +1000 Subject: [PATCH 5/9] fix NPE error comparing value sets --- .../main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java index 674db72ab..7ce597134 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java @@ -545,8 +545,7 @@ public class ValueSetComparer extends CanonicalResourceComparer { if (t.hasLeft() && t.hasRight()) { ConceptSetComponent csL = (ConceptSetComponent) t.getLeft(); ConceptSetComponent csR = (ConceptSetComponent) t.getRight(); - // we assume both have systems - if (csL.getSystem().equals(csR.getSystem())) { + if (csL.hasSystem() && csL.getSystem().equals(csR.getSystem())) { r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).span(2).center()); } else { r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); From 7616b5521364391aa99628e77e8de0216096f743 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 3 Aug 2020 17:36:32 +1000 Subject: [PATCH 6/9] * Rendering: add rendering for Parameters resources * Rendering: refactor of resource resolution code to support Parameters * General clean up of rendering consistency & implement additional details when rendering (including patient summary) * Rendering: major overhaul of DiagnosticReport rendering --- .../hl7/fhir/r5/renderers/BundleRenderer.java | 9 +- .../CapabilityStatementRenderer.java | 13 +- .../fhir/r5/renderers/CodeSystemRenderer.java | 3 +- .../CompartmentDefinitionRenderer.java | 13 +- .../fhir/r5/renderers/ConceptMapRenderer.java | 3 +- .../hl7/fhir/r5/renderers/DataRenderer.java | 73 ++-- .../renderers/DiagnosticReportRenderer.java | 350 +++++++++++++++--- .../fhir/r5/renderers/EncounterRenderer.java | 9 +- .../ImplementationGuideRenderer.java | 13 +- .../hl7/fhir/r5/renderers/LiquidRenderer.java | 12 +- .../hl7/fhir/r5/renderers/ListRenderer.java | 10 +- .../r5/renderers/NamingSystemRenderer.java | 13 +- .../OperationDefinitionRenderer.java | 2 +- .../renderers/OperationOutcomeRenderer.java | 8 +- .../fhir/r5/renderers/ParametersRenderer.java | 124 +++++++ .../fhir/r5/renderers/PatientRenderer.java | 181 ++++++++- .../r5/renderers/ProfileDrivenRenderer.java | 9 +- .../fhir/r5/renderers/ProvenanceRenderer.java | 10 +- .../r5/renderers/QuestionnaireRenderer.java | 2 +- .../QuestionnaireResponseRenderer.java | 7 +- .../org/hl7/fhir/r5/renderers/Renderer.java | 23 ++ .../fhir/r5/renderers/ResourceRenderer.java | 26 +- .../StructureDefinitionRenderer.java | 13 +- .../r5/renderers/TerminologyRenderer.java | 11 + .../fhir/r5/renderers/ValueSetRenderer.java | 2 +- 25 files changed, 835 insertions(+), 104 deletions(-) create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ParametersRenderer.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java index 48c0a8c14..dca727389 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java @@ -37,8 +37,8 @@ public class BundleRenderer extends ResourceRenderer { } @Override - public boolean render(XhtmlNode x, DomainResource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { - return false; + public boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { + return render(x, (Bundle) r); } @Override @@ -46,6 +46,11 @@ public class BundleRenderer extends ResourceRenderer { return null; } + @Override + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + return null; + } + @Override public boolean render(XhtmlNode x, ResourceWrapper b) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { List entries = b.children("entry"); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CapabilityStatementRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CapabilityStatementRenderer.java index 8da8bfbbc..31251cb85 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CapabilityStatementRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CapabilityStatementRenderer.java @@ -15,6 +15,7 @@ import org.hl7.fhir.r5.model.CapabilityStatement.SystemInteractionComponent; import org.hl7.fhir.r5.model.CapabilityStatement.SystemRestfulInteraction; import org.hl7.fhir.r5.model.CapabilityStatement.TypeRestfulInteraction; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; @@ -29,7 +30,7 @@ public class CapabilityStatementRenderer extends ResourceRenderer { super(context, rcontext); } - public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { return render(x, (CapabilityStatement) dr); } @@ -149,4 +150,14 @@ public class CapabilityStatementRenderer extends ResourceRenderer { tr.td().addText(value); } + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + if (r.has("title")) { + return r.children("title").get(0).getBase().primitiveValue(); + } + if (r.has("name")) { + return r.children("name").get(0).getBase().primitiveValue(); + } + return "??"; + } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java index 4abbe0c1b..065bdacba 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java @@ -19,6 +19,7 @@ import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Enumeration; import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; @@ -38,7 +39,7 @@ public class CodeSystemRenderer extends TerminologyRenderer { } - public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { return render(x, (CodeSystem) dr); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CompartmentDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CompartmentDefinitionRenderer.java index 4d83b631a..03cd7f507 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CompartmentDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CompartmentDefinitionRenderer.java @@ -11,6 +11,7 @@ import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.r5.model.CompartmentDefinition; import org.hl7.fhir.r5.model.CompartmentDefinition.CompartmentDefinitionResourceComponent; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.xhtml.XhtmlNode; @@ -26,7 +27,7 @@ public class CompartmentDefinitionRenderer extends ResourceRenderer { super(context, rcontext); } - public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { return render(x, (CompartmentDefinition) dr); } @@ -72,4 +73,14 @@ public class CompartmentDefinitionRenderer extends ResourceRenderer { return ((CompartmentDefinition) r).present(); } + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + if (r.has("title")) { + return r.children("title").get(0).getBase().primitiveValue(); + } + if (r.has("name")) { + return r.children("name").get(0).getBase().primitiveValue(); + } + return "??"; + } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ConceptMapRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ConceptMapRenderer.java index a3221f056..4798d1155 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ConceptMapRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ConceptMapRenderer.java @@ -18,6 +18,7 @@ import org.hl7.fhir.r5.model.ContactDetail; import org.hl7.fhir.r5.model.ContactPoint; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship; +import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.r5.utils.ToolingExtensions; @@ -34,7 +35,7 @@ public class ConceptMapRenderer extends TerminologyRenderer { super(context, rcontext); } - public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { return render(x, (ConceptMap) dr); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java index 2d1c905b0..d3748aa42 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java @@ -1,6 +1,7 @@ package org.hl7.fhir.r5.renderers; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.util.List; import org.hl7.fhir.exceptions.DefinitionException; @@ -11,6 +12,7 @@ import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; import org.hl7.fhir.r5.model.Address; import org.hl7.fhir.r5.model.Annotation; import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.BaseDateTimeType; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeableConcept; @@ -23,6 +25,7 @@ import org.hl7.fhir.r5.model.Enumeration; import org.hl7.fhir.r5.model.HumanName; import org.hl7.fhir.r5.model.HumanName.NameUse; import org.hl7.fhir.r5.model.Identifier; +import org.hl7.fhir.r5.model.InstantType; import org.hl7.fhir.r5.model.Period; import org.hl7.fhir.r5.model.PrimitiveType; import org.hl7.fhir.r5.model.Quantity; @@ -50,7 +53,7 @@ import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.utilities.xhtml.XhtmlParser; -public class DataRenderer { +public class DataRenderer extends Renderer { // -- 1. context -------------------------------------------------------------- @@ -193,7 +196,6 @@ public class DataRenderer { return lang; } - private boolean isCanonical(String path) { if (!path.endsWith(".url")) return false; @@ -269,8 +271,19 @@ public class DataRenderer { return "to do"; } - public void render(XhtmlNode x, BaseWrapper type) { - x.tx("to do"); + public void render(XhtmlNode x, BaseWrapper type) { + Base base = null; + try { + base = type.getBase(); + } catch (FHIRException | IOException e) { + x.tx("Error: " + e.getMessage()); // this shouldn't happen - it's an error in the library itself + return; + } + if (base instanceof DataType) { + render(x, (DataType) base); + } else { + x.tx("to do: "+base.fhirType()); + } } public void renderBase(XhtmlNode x, Base b) { @@ -312,6 +325,10 @@ public class DataRenderer { renderSampledData(x, (SampledData) type); } else if (type instanceof Reference) { renderReference(x, (Reference) type); + } else if (type instanceof InstantType) { + x.tx(((InstantType) type).toHumanDisplay()); + } else if (type instanceof BaseDateTimeType) { + x.tx(((BaseDateTimeType) type).toHumanDisplay()); } else if (type.isPrimitive()) { x.tx(type.primitiveValue()); } else { @@ -367,31 +384,37 @@ public class DataRenderer { } protected void renderAnnotation(XhtmlNode x, Annotation a, boolean showCodeDetails) throws FHIRException { - StringBuilder s = new StringBuilder(); - if (a.hasAuthor()) { - s.append("Author: "); + StringBuilder b = new StringBuilder(); + if (a.hasText()) { + b.append(a.getText()); + } - if (a.hasAuthorReference()) - s.append(a.getAuthorReference().getReference()); - else if (a.hasAuthorStringType()) - s.append(a.getAuthorStringType().getValue()); + if (a.hasText() && (a.hasAuthor() || a.hasTimeElement())) { + b.append(" ("); + } + + if (a.hasAuthor()) { + b.append("By "); + if (a.hasAuthorReference()) { + b.append(a.getAuthorReference().getReference()); + } else if (a.hasAuthorStringType()) { + b.append(a.getAuthorStringType().getValue()); + } } if (a.hasTimeElement()) { - if (s.length() > 0) - s.append("; "); - - s.append("Made: ").append(a.getTimeElement().toHumanDisplay()); + if (b.length() > 0) { + b.append(" "); + } + b.append("@").append(a.getTimeElement().toHumanDisplay()); + } + if (a.hasText() && (a.hasAuthor() || a.hasTimeElement())) { + b.append(")"); } - if (a.hasText()) { - if (s.length() > 0) - s.append("; "); - s.append("Annotation: ").append(a.getText()); - } - x.addText(s.toString()); + x.addText(b.toString()); } public String displayCoding(Coding c) { @@ -563,11 +586,13 @@ public class DataRenderer { if (ii.hasType()) { if (ii.getType().hasText()) - s = ii.getType().getText()+" = "+s; + s = ii.getType().getText()+": "+s; else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasDisplay()) - s = ii.getType().getCoding().get(0).getDisplay()+" = "+s; + s = ii.getType().getCoding().get(0).getDisplay()+": "+s; else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasCode()) - s = lookupCode(ii.getType().getCoding().get(0).getSystem(), ii.getType().getCoding().get(0).getCode())+" = "+s; + s = lookupCode(ii.getType().getCoding().get(0).getSystem(), ii.getType().getCoding().get(0).getCode())+": "+s; + } else { + s = "id: "+s; } if (ii.hasUse()) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DiagnosticReportRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DiagnosticReportRenderer.java index 5b1019613..9a2daa048 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DiagnosticReportRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DiagnosticReportRenderer.java @@ -10,6 +10,8 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.DataType; import org.hl7.fhir.r5.model.DiagnosticReport; import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper; @@ -18,6 +20,7 @@ import org.hl7.fhir.r5.renderers.utils.DirectWrappers; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceWithReference; +import org.hl7.fhir.r5.utils.EOperationOutcome; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.xhtml.XhtmlNode; @@ -38,11 +41,11 @@ public class DiagnosticReportRenderer extends ResourceRenderer { super(context, rcontext); } - public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws IOException, FHIRException, EOperationOutcome { return render(x, (DiagnosticReport) dr); } - public boolean render(XhtmlNode x, ResourceWrapper dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, ResourceWrapper dr) throws IOException, FHIRException, EOperationOutcome { XhtmlNode h2 = x.h2(); render(h2, getProperty(dr, "code").value()); h2.tx(" "); @@ -56,40 +59,55 @@ public class DiagnosticReportRenderer extends ResourceRenderer { } h2.tx(") "); } + XhtmlNode tbl = x.table("grid"); + XhtmlNode tr; + if (dr.has("subject")) { + tr = tbl.tr(); + tr.td().tx("Subject"); + populateSubjectSummary(tr.td(), getProperty(dr, "subject").value()); + } + + DataType eff = null; + DataType iss = null; + + if (dr.has("effective[x]")) { + tr = tbl.tr(); + tr.td().tx("When For"); + eff = (DataType) getProperty(dr, "effective[x]").value().getBase(); + render(tr.td(), eff); + } if (dr.has("issued")) { - render(h2, getProperty(dr, "issued").value()); + tr = tbl.tr(); + tr.td().tx("Reported"); + eff = (DataType) getProperty(dr, "effective[x]").value().getBase(); + render(tr.td(), getProperty(dr, "issued").value()); } - XhtmlNode tbl = x.table("grid"); - XhtmlNode tr = tbl.tr(); - XhtmlNode tdl = tr.td(); - XhtmlNode tdr = tr.td(); - if (dr.has("subject")) { - populateSubjectSummary(tdl, getProperty(dr, "subject").value()); - } - tdr.b().tx("Report Details"); - tdr.br(); pw = getProperty(dr, "perfomer"); if (valued(pw)) { - tdr.addText(Utilities.pluralize("Performer", pw.getValues().size())+":"); + tr = tbl.tr(); + tr.td().tx(Utilities.pluralize("Performer", pw.getValues().size())); + XhtmlNode tdr = tr.td(); for (BaseWrapper v : pw.getValues()) { tdr.tx(" "); render(tdr, v); } - tdr.br(); } pw = getProperty(dr, "identifier"); if (valued(pw)) { - tdr.addText(Utilities.pluralize("Identifier", pw.getValues().size())+":"); + tr = tbl.tr(); + tr.td().tx(Utilities.pluralize("Identifier", pw.getValues().size())+":"); + XhtmlNode tdr = tr.td(); for (BaseWrapper v : pw.getValues()) { tdr.tx(" "); render(tdr, v); } - tdr.br(); } pw = getProperty(dr, "request"); if (valued(pw)) { - tdr.addText(Utilities.pluralize("Request", pw.getValues().size())+":"); + tr = tbl.tr(); + tr.td().tx(Utilities.pluralize("Request", pw.getValues().size())+":"); + XhtmlNode tdr = tr.td(); for (BaseWrapper v : pw.getValues()) { tdr.tx(" "); render(tdr, v); @@ -97,29 +115,35 @@ public class DiagnosticReportRenderer extends ResourceRenderer { tdr.br(); } + + x.para().b().tx("Report Details"); + pw = getProperty(dr, "result"); if (valued(pw)) { List observations = fetchObservations(pw.getValues(), dr); - buildObservationsTable(x, observations); + buildObservationsTable(x, observations, eff, iss); } pw = getProperty(dr, "conclusion"); if (valued(pw)) render(x.para(), pw.value()); - pw = getProperty(dr, "result"); + pw = getProperty(dr, "conclusionCode"); + if (!valued(pw)) { + pw = getProperty(dr, "codedDiagnosis"); + } if (valued(pw)) { XhtmlNode p = x.para(); - p.b().tx("Coded Diagnoses :"); + p.b().tx("Coded Conclusions :"); + XhtmlNode ul = x.ul(); for (BaseWrapper v : pw.getValues()) { - tdr.tx(" "); - render(tdr, v); + render(ul.li(), v); } } return false; } - public boolean render(XhtmlNode x, DiagnosticReport dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, DiagnosticReport dr) throws IOException, FHIRException, EOperationOutcome { render(x, new DirectWrappers.ResourceWrapperDirect(this.context, dr)); return true; @@ -138,7 +162,12 @@ public class DiagnosticReportRenderer extends ResourceRenderer { return display((DiagnosticReport) r); } - private void populateSubjectSummary(XhtmlNode container, BaseWrapper subject) throws UnsupportedEncodingException, FHIRException, IOException { + @Override + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + return "Not done yet"; + } + + private void populateSubjectSummary(XhtmlNode container, BaseWrapper subject) throws UnsupportedEncodingException, FHIRException, IOException, EOperationOutcome { ResourceWrapper r = fetchResource(subject); if (r == null) container.tx("Unable to get Patient Details"); @@ -148,8 +177,8 @@ public class DiagnosticReportRenderer extends ResourceRenderer { container.tx("Not done yet"); } - private void generatePatientSummary(XhtmlNode c, ResourceWrapper r) { - c.tx("to do"); + private void generatePatientSummary(XhtmlNode c, ResourceWrapper r) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { + new PatientRenderer(context).describe(c, r); } private List fetchObservations(List list, ResourceWrapper rw) throws UnsupportedEncodingException, FHIRException, IOException { @@ -171,40 +200,165 @@ public class DiagnosticReportRenderer extends ResourceRenderer { return res; } - private void buildObservationsTable(XhtmlNode root, List observations) { - XhtmlNode tbl = root.table( "none"); + private void buildObservationsTable(XhtmlNode root, List observations, DataType eff, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { + XhtmlNode tbl = root.table("grid"); + boolean refRange = scanObsForRefRange(observations); + boolean flags = scanObsForFlags(observations); + boolean note = scanObsForNote(observations); + boolean effectiveTime = scanObsForEffective(observations, eff); + boolean issued = scanObsForIssued(observations, iss); + int cs = 2; + if (refRange) cs++; + if (flags) cs++; + if (note) cs++; + if (issued) cs++; + if (effectiveTime) cs++; + XhtmlNode tr = tbl.tr(); + tr.td().b().tx("Code"); + tr.td().b().tx("Value"); + if (refRange) { + tr.td().b().tx("Reference Range"); + } + if (flags) { + tr.td().b().tx("Flags"); + } + if (note) { + tr.td().b().tx("Note"); + } + if (effectiveTime) { + tr.td().b().tx("When For"); + } + if (issued) { + tr.td().b().tx("Reported"); + } for (ObservationNode o : observations) { - addObservationToTable(tbl, o, 0); + addObservationToTable(tbl, o, 0, Integer.toString(cs), refRange, flags, note, effectiveTime, issued, eff, iss); } } - private void addObservationToTable(XhtmlNode tbl, ObservationNode o, int i) { + private boolean scanObsForRefRange(List observations) { + for (ObservationNode o : observations) { + if (o.obs.getResource() != null) { + PropertyWrapper pw = getProperty(o.obs.getResource(), "referenceRange"); + if (valued(pw)) { + return true; + } + } + if (o.contained != null) { + if (scanObsForRefRange(o.contained)) { + return true; + } + } + } + return false; + } + + private boolean scanObsForNote(List observations) { + for (ObservationNode o : observations) { + if (o.obs.getResource() != null) { + PropertyWrapper pw = getProperty(o.obs.getResource(), "note"); + if (valued(pw)) { + return true; + } + } + if (o.contained != null) { + if (scanObsForNote(o.contained)) { + return true; + } + } + } + return false; + } + + private boolean scanObsForIssued(List observations, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { + for (ObservationNode o : observations) { + if (o.obs.getResource() != null) { + PropertyWrapper pw = getProperty(o.obs.getResource(), "issued"); + if (valued(pw)) { + if (!Base.compareDeep(pw.value().getBase(), iss, true)) { + return true; + } + } + } + if (o.contained != null) { + if (scanObsForIssued(o.contained, iss)) { + return true; + } + } + } + return false; + } + + private boolean scanObsForEffective(List observations, DataType eff) throws UnsupportedEncodingException, FHIRException, IOException { + for (ObservationNode o : observations) { + if (o.obs.getResource() != null) { + PropertyWrapper pw = getProperty(o.obs.getResource(), "effective[x]"); + if (valued(pw)) { + if (!Base.compareDeep(pw.value().getBase(), eff, true)) { + return true; + } + } + } + if (o.contained != null) { + if (scanObsForEffective(o.contained, eff)) { + return true; + } + } + } + return false; + } + + private boolean scanObsForFlags(List observations) throws UnsupportedEncodingException, FHIRException, IOException { + for (ObservationNode o : observations) { + if (o.obs.getResource() != null) { + PropertyWrapper pw = getProperty(o.obs.getResource(), "interpretation"); + if (valued(pw)) { + return true; + } + pw = getProperty(o.obs.getResource(), "status"); + if (valued(pw)) { + if (!pw.value().getBase().primitiveValue().equals("final")) { + return true; + } + } + + } + if (o.contained != null) { + if (scanObsForFlags(o.contained)) { + return true; + } + } + } + return false; + } + + private void addObservationToTable(XhtmlNode tbl, ObservationNode o, int i, String cs, boolean refRange, boolean flags, boolean note, boolean effectiveTime, boolean issued, DataType eff, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { XhtmlNode tr = tbl.tr(); if (o.obs.getReference() == null) { - XhtmlNode td = tr.td().colspan("6"); + XhtmlNode td = tr.td().colspan(cs); td.i().tx("This Observation could not be resolved"); } else { if (o.obs.getResource() != null) { - addObservationToTable(tr, o.obs.getResource(), i); + addObservationToTable(tr, o.obs.getResource(), i, o.obs.getReference(), refRange, flags, note, effectiveTime, issued, eff, iss); } else { - tr.td().tx("Unable to resolve Observation: "+o.obs.getReference()); + XhtmlNode td = tr.td().colspan(cs); + td.i().ah(o.obs.getReference()).tx("Observation"); } if (o.contained != null) { for (ObservationNode c : o.contained) { - addObservationToTable(tbl, c, i+1); + addObservationToTable(tbl, c, i+1, cs, refRange, flags, note, effectiveTime, issued, eff, iss); } } } } - private void addObservationToTable(XhtmlNode tr, ResourceWrapper obs, int i) { - // TODO Auto-generated method stub + private void addObservationToTable(XhtmlNode tr, ResourceWrapper obs, int i, String ref, boolean refRange, boolean flags, boolean note, boolean effectiveTime, boolean issued, DataType eff, DataType iss) throws UnsupportedEncodingException, FHIRException, IOException { // code (+bodysite) XhtmlNode td = tr.td(); PropertyWrapper pw = getProperty(obs, "code"); if (valued(pw)) { - render(td, pw.value()); + render(td.ah(ref), pw.value()); } pw = getProperty(obs, "bodySite"); if (valued(pw)) { @@ -218,23 +372,113 @@ public class DiagnosticReportRenderer extends ResourceRenderer { pw = getProperty(obs, "value[x]"); if (valued(pw)) { render(td, pw.value()); + } else { + pw = getProperty(obs, "dataAbsentReason"); + if (valued(pw)) { + XhtmlNode span = td.span("color: maroon", "Error"); + span.tx("Error: "); + render(span.b(), pw.value()); + } + } + if (refRange) { + // reference range + td = tr.td(); + pw = getProperty(obs, "referenceRange"); + if (valued(pw)) { + boolean first = true; + for (BaseWrapper v : pw.getValues()) { + if (first) first = false; else td.br(); + PropertyWrapper pwr = getProperty(v, "type"); + if (valued(pwr)) { + render(td, pwr.value()); + td.tx(": "); + } + PropertyWrapper pwt = getProperty(v, "text"); + if (valued(pwt)) { + render(td, pwt.value()); + } else { + PropertyWrapper pwl = getProperty(v, "low"); + PropertyWrapper pwh = getProperty(v, "high"); + if (valued(pwl) && valued(pwh)) { + render(td, pwl.value()); + td.tx(" - "); + render(td, pwh.value()); + } else if (valued(pwl)) { + td.tx(">"); + render(td, pwl.value()); + } else if (valued(pwh)) { + td.tx("<"); + render(td, pwh.value()); + } else { + td.tx("??"); + } + } + pwr = getProperty(v, "appliesTo"); + PropertyWrapper pwrA = getProperty(v, "age"); + if (valued(pwr) || valued(pwrA)) { + boolean firstA = true; + td.tx(" for "); + if (valued(pwr)) { + for (BaseWrapper va : pwr.getValues()) { + if (firstA) firstA = false; else td.tx(", "); + render(td, va); + } + } + if (valued(pwrA)) { + if (firstA) firstA = false; else td.tx(", "); + td.tx("Age "); + render(td, pwrA.value()); + } + } + } + } + } + if (flags) { + // flags (status other than F, interpretation, ) + td = tr.td(); + boolean first = true; + pw = getProperty(obs, "status"); + if (valued(pw)) { + if (!pw.value().getBase().primitiveValue().equals("final")) { + if (first) first = false; else td.br(); + render(td, pw.value()); + } + } + pw = getProperty(obs, "interpretation"); + if (valued(pw)) { + for (BaseWrapper v : pw.getValues()) { + if (first) first = false; else td.br(); + render(td, v); + } + } } - // units - td = tr.td(); - td.tx("to do"); - - // reference range - td = tr.td(); - td.tx("to do"); - - // flags (status other than F, interpretation, ) - td = tr.td(); - td.tx("to do"); - - // issued if different to DR - td = tr.td(); - td.tx("to do"); + if (note) { + td = tr.td(); + pw = getProperty(obs, "note"); + if (valued(pw)) { + render(td, pw.value()); + } + } + if (effectiveTime) { + // effective if different to DR + td = tr.td(); + pw = getProperty(obs, "effective[x]"); + if (valued(pw)) { + if (!Base.compareDeep(pw.value().getBase(), eff, true)) { + render(td, pw.value()); + } + } + } + if (issued) { + // issued if different to DR + td = tr.td(); + pw = getProperty(obs, "issued"); + if (valued(pw)) { + if (!Base.compareDeep(pw.value().getBase(), eff, true)) { + render(td, pw.value()); + } + } + } } - } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/EncounterRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/EncounterRenderer.java index 4d2ad7023..a735d62af 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/EncounterRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/EncounterRenderer.java @@ -6,6 +6,7 @@ import java.io.UnsupportedEncodingException; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class EncounterRenderer extends ResourceRenderer { @@ -15,7 +16,7 @@ public class EncounterRenderer extends ResourceRenderer { super(context); } - public boolean render(XhtmlNode x, DomainResource dr) throws UnsupportedEncodingException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws UnsupportedEncodingException, IOException { describe(x, dr); return false; } @@ -24,4 +25,10 @@ public class EncounterRenderer extends ResourceRenderer { return "Not done yet"; } + @Override + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + return "Not done yet"; + } + + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ImplementationGuideRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ImplementationGuideRenderer.java index cb47da26e..ff9a61d81 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ImplementationGuideRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ImplementationGuideRenderer.java @@ -9,6 +9,7 @@ import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.ImplementationGuide; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.utilities.xhtml.XhtmlNode; @@ -22,7 +23,7 @@ public class ImplementationGuideRenderer extends ResourceRenderer { super(context, rcontext); } - public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { return render(x, (ImplementationGuide) dr); } @@ -47,4 +48,14 @@ public class ImplementationGuideRenderer extends ResourceRenderer { return ((ImplementationGuide) r).present(); } + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + if (r.has("title")) { + return r.children("title").get(0).getBase().primitiveValue(); + } + if (r.has("name")) { + return r.children("name").get(0).getBase().primitiveValue(); + } + return "??"; + } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/LiquidRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/LiquidRenderer.java index 412a74e23..78893a2b4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/LiquidRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/LiquidRenderer.java @@ -34,7 +34,7 @@ public class LiquidRenderer extends ResourceRenderer { } @Override - public boolean render(XhtmlNode x, DomainResource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { + public boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { LiquidEngine engine = new LiquidEngine(context.getWorker(), context.getServices()); XhtmlNode xn; try { @@ -56,6 +56,16 @@ public class LiquidRenderer extends ResourceRenderer { return "not done yet"; } + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + if (r.has("title")) { + return r.children("title").get(0).getBase().primitiveValue(); + } + if (r.has("name")) { + return r.children("name").get(0).getBase().primitiveValue(); + } + return "??"; + } + @Override public boolean render(XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { LiquidEngine engine = new LiquidEngine(context.getWorker(), context.getServices()); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ListRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ListRenderer.java index fe0555fd1..18c0c24ec 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ListRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ListRenderer.java @@ -29,7 +29,7 @@ public class ListRenderer extends ResourceRenderer { super(context, rcontext); } - public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { return render(x, (ListResource) dr); } @@ -189,6 +189,14 @@ public class ListRenderer extends ResourceRenderer { return ((ListResource) r).getTitle(); } + @Override + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + if (r.has("title")) { + return r.children("title").get(0).getBase().primitiveValue(); + } + return "??"; + } + private void shortForRef(XhtmlNode x, Reference ref) throws UnsupportedEncodingException, IOException { ResourceWithReference r = context.getResolver().resolve(context, ref.getReference()); if (r == null) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/NamingSystemRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/NamingSystemRenderer.java index f731d4f2b..59b69e8c1 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/NamingSystemRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/NamingSystemRenderer.java @@ -12,6 +12,7 @@ import org.hl7.fhir.r5.model.PrimitiveType; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; import org.hl7.fhir.r5.utils.ToolingExtensions; @@ -28,7 +29,7 @@ public class NamingSystemRenderer extends ResourceRenderer { super(context, rcontext); } - public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { return render(x, (NamingSystem) dr); } @@ -125,4 +126,14 @@ public class NamingSystemRenderer extends ResourceRenderer { return ((NamingSystem) r).present(); } + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + if (r.has("title")) { + return r.children("title").get(0).getBase().primitiveValue(); + } + if (r.has("name")) { + return r.children("name").get(0).getBase().primitiveValue(); + } + return "??"; + } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/OperationDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/OperationDefinitionRenderer.java index 66b705b9c..630b14f9b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/OperationDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/OperationDefinitionRenderer.java @@ -30,7 +30,7 @@ public class OperationDefinitionRenderer extends TerminologyRenderer { super(context, rcontext); } - public boolean render(XhtmlNode x, DomainResource dr) throws IOException, FHIRException, EOperationOutcome { + public boolean render(XhtmlNode x, Resource dr) throws IOException, FHIRException, EOperationOutcome { return render(x, (OperationDefinition) dr); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/OperationOutcomeRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/OperationOutcomeRenderer.java index 6139cbb97..40cc99790 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/OperationOutcomeRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/OperationOutcomeRenderer.java @@ -14,6 +14,7 @@ import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; @@ -29,7 +30,7 @@ public class OperationOutcomeRenderer extends ResourceRenderer { super(context, rcontext); } - public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { return render(x, (OperationOutcome) dr); } @@ -85,6 +86,11 @@ public class OperationOutcomeRenderer extends ResourceRenderer { return "todo"; } + @Override + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + return "Not done yet"; + } + @Override public String display(Resource r) throws UnsupportedEncodingException, IOException { return display((OperationOutcome) r); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ParametersRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ParametersRenderer.java new file mode 100644 index 000000000..0cbc2b635 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ParametersRenderer.java @@ -0,0 +1,124 @@ +package org.hl7.fhir.r5.renderers; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +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.elementmodel.Element; +import org.hl7.fhir.r5.model.Bundle; +import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r5.model.Bundle.BundleEntryRequestComponent; +import org.hl7.fhir.r5.model.Bundle.BundleEntryResponseComponent; +import org.hl7.fhir.r5.model.Bundle.BundleEntrySearchComponent; +import org.hl7.fhir.r5.model.Bundle.BundleType; +import org.hl7.fhir.r5.model.Composition; +import org.hl7.fhir.r5.model.DomainResource; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; +import org.hl7.fhir.r5.model.Provenance; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; +import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; +import org.hl7.fhir.r5.utils.EOperationOutcome; +import org.hl7.fhir.utilities.xhtml.NodeType; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; + +public class ParametersRenderer extends ResourceRenderer { + + public ParametersRenderer(RenderingContext context, ResourceContext rcontext) { + super(context, rcontext); + } + + + @Override + public boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { + return false; + } + + @Override + public String display(Resource r) throws UnsupportedEncodingException, IOException { + return null; + } + + @Override + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + return null; + } + + @Override + public boolean render(XhtmlNode x, ResourceWrapper params) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { + x.h2().tx("Parameters"); + XhtmlNode tbl = x.table("grid"); + PropertyWrapper pw = getProperty(params, "parameter"); + if (valued(pw)) { + paramsW(tbl, pw.getValues(), 0); + } + return false; + } + + private void paramsW(XhtmlNode tbl, List list, int indent) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { + for (BaseWrapper p : list) { + XhtmlNode tr = tbl.tr(); + XhtmlNode td = tr.td(); + for (int i = 0; i < indent; i++) { + td.tx(XhtmlNode.NBSP); + } + td.tx(p.get("name").primitiveValue()); + if (p.has("value")) { + renderBase(tr.td(), p.get("value")); + } else if (p.has("resource")) { + ResourceWrapper rw = p.getChildByName("resource").getAsResource(); + td = tr.td(); + XhtmlNode para = td.para(); + para.tx(rw.fhirType()+"/"+rw.getId()); + para.an(rw.fhirType().toLowerCase()+"_"+rw.getId()).tx(" "); + XhtmlNode x = rw.getNarrative(); + if (x != null) { + td.addChildren(x); + } else { + ResourceRenderer rr = RendererFactory.factory(rw, context, rcontext); + rr.render(td, rw); + } + } else if (p.has("part")) { + tr.td(); + PropertyWrapper pw = getProperty(p, "parameter"); + paramsW(tbl, pw.getValues(), 1); + } + } + } + + public XhtmlNode render(Parameters params) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { + XhtmlNode div = new XhtmlNode(NodeType.Element, "div"); + div.h2().tx("Parameters"); + XhtmlNode tbl = div.table("grid"); + params(tbl, params.getParameter(), 0); + return div; + } + + private void params(XhtmlNode tbl, List list, int indent) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { + for (ParametersParameterComponent p : list) { + XhtmlNode tr = tbl.tr(); + XhtmlNode td = tr.td(); + for (int i = 0; i < indent; i++) { + td.tx(XhtmlNode.NBSP); + } + td.tx(p.getName()); + if (p.hasValue()) { + render(tr.td(), p.getValue()); + } else if (p.hasResource()) { + ResourceRenderer rr = RendererFactory.factory(p.getResource(), context); + rr.render(tr.td(), p.getResource()); + } else if (p.hasPart()) { + tr.td(); + params(tbl, p.getPart(), 1); + } + } + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/PatientRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/PatientRenderer.java index 449c31d12..577321914 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/PatientRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/PatientRenderer.java @@ -3,9 +3,20 @@ package org.hl7.fhir.r5.renderers; import java.io.IOException; import java.io.UnsupportedEncodingException; +import org.apache.poi.hssf.record.chart.DatRecord; +import org.hl7.fhir.r5.model.DateTimeType; +import org.hl7.fhir.r5.model.DateType; import org.hl7.fhir.r5.model.DomainResource; +import org.hl7.fhir.r5.model.HumanName; +import org.hl7.fhir.r5.model.HumanName.NameUse; +import org.hl7.fhir.r5.model.Identifier; +import org.hl7.fhir.r5.model.Identifier.IdentifierUse; +import org.hl7.fhir.r5.model.Patient; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class PatientRenderer extends ResourceRenderer { @@ -16,13 +27,179 @@ public class PatientRenderer extends ResourceRenderer { super(context); } - public boolean render(XhtmlNode x, DomainResource dr) throws UnsupportedEncodingException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws UnsupportedEncodingException, IOException { describe(x, dr); return false; } + // name gender DoB (MRN) public String display(Resource dr) { - return "Not done yet"; + Patient pat = (Patient) dr; + Identifier id = null; + for (Identifier t : pat.getIdentifier()) { + id = chooseId(id, t); + } + HumanName n = null; + for (HumanName t : pat.getName()) { + n = chooseName(n, t); + } + return display(n, pat.getGender().getDisplay(), pat.getBirthDateElement(), id); } + private Identifier chooseId(Identifier oldId, Identifier newId) { + if (oldId == null) { + return newId; + } + if (newId == null) { + return oldId; + } + return isPreferred(newId.getUse(), oldId.getUse()) ? newId : oldId; + } + + private boolean isPreferred(IdentifierUse newUse, IdentifierUse oldUse) { + if (newUse == null && oldUse == null || newUse == oldUse) { + return false; + } + if (newUse == null) { + return true; + } + switch (newUse) { + case NULL: return !existsInList(oldUse, IdentifierUse.OFFICIAL, IdentifierUse.USUAL); + case OFFICIAL: return !existsInList(oldUse, IdentifierUse.USUAL); + case OLD: return !existsInList(oldUse, IdentifierUse.OFFICIAL, IdentifierUse.SECONDARY, IdentifierUse.USUAL); + case SECONDARY: return !existsInList(oldUse, IdentifierUse.OFFICIAL, IdentifierUse.USUAL); + case TEMP: return !existsInList(oldUse, IdentifierUse.OFFICIAL, IdentifierUse.SECONDARY, IdentifierUse.USUAL); + case USUAL: return true; + default: return false; + } + } + + private boolean existsInList(IdentifierUse oldUse, IdentifierUse... values) { + for (IdentifierUse value : values) { + if (value == oldUse) { + return true; + } + } + return false; + } + + private HumanName chooseName(HumanName oldName, HumanName newName) { + if (oldName == null) { + return newName; + } + if (newName == null) { + return oldName; + } + return isPreferred(newName.getUse(), oldName.getUse()) ? newName : oldName; + } + + + private boolean isPreferred(NameUse newUse, NameUse oldUse) { + if (newUse == null && oldUse == null || newUse == oldUse) { + return false; + } + if (newUse == null) { + return true; + } + switch (oldUse) { + case ANONYMOUS: return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL); + case MAIDEN: return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL); + case NICKNAME: return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL); + case NULL: return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL); + case OFFICIAL: return existsInList(newUse, NameUse.USUAL); + case OLD: return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL); + case TEMP: return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL); + case USUAL: return false; + } + return false; + } + + private boolean existsInList(NameUse oldUse, NameUse... values) { + for (NameUse value : values) { + if (value == oldUse) { + return true; + } + } + return false; + } + + @Override + public String display(ResourceWrapper pat) throws UnsupportedEncodingException, IOException { + Identifier id = null; + PropertyWrapper pw = getProperty(pat, "identifier"); + for (BaseWrapper t : pw.getValues()) { + id = chooseId(id, (Identifier) t.getBase()); + } + pw = getProperty(pat, "name"); + HumanName n = null; + for (BaseWrapper t : pw.getValues()) { + n = chooseName(n, (HumanName) t); + } + String gender = null; + pw = getProperty(pat, "gender"); + if (valued(pw)) { + pw.value().getBase().primitiveValue(); + } + DateType dt = null; + pw = getProperty(pat, "birthDate"); + if (valued(pw)) { + dt = (DateType) pw.value().getBase(); + } + return display(n, gender, dt, id); + } + + public void describe(XhtmlNode x, ResourceWrapper pat) throws UnsupportedEncodingException, IOException { + Identifier id = null; + PropertyWrapper pw = getProperty(pat, "identifier"); + for (BaseWrapper t : pw.getValues()) { + id = chooseId(id, (Identifier) t.getBase()); + } + pw = getProperty(pat, "name"); + HumanName n = null; + for (BaseWrapper t : pw.getValues()) { + n = chooseName(n, (HumanName) t.getBase()); + } + String gender = null; + pw = getProperty(pat, "gender"); + if (valued(pw)) { + pw.value().getBase().primitiveValue(); + } + DateType dt = null; + pw = getProperty(pat, "birthDate"); + if (valued(pw)) { + dt = (DateType) pw.value().getBase(); + } + describe(x, n, gender, dt, id); + } + + + private String display(HumanName name, String gender, DateType dob, Identifier id) { + StringBuilder b = new StringBuilder(); + b.append(display(name)); + b.append(" "); + b.append(gender); + b.append(" "); + b.append(display(dob)); + if (id != null) { + b.append(" ( "); + b.append(display(id)); + b.append(")"); + } + return b.toString(); + } + + public void describe(XhtmlNode x, HumanName name, String gender, DateType dob, Identifier id) throws UnsupportedEncodingException, IOException { + render(x.b(), name); + x.tx(" "); + x.tx(gender); + x.tx(" "); + render(x, dob); + if (id != null) { + x.tx(" ( "); + render(x, id); + x.tx(")"); + } + } + + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java index 1ee3f4e45..4cccb77a8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java @@ -92,7 +92,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } @Override - public boolean render(XhtmlNode x, DomainResource r) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException { return render(x, new DirectWrappers.ResourceWrapperDirect(context, r)); } @@ -109,7 +109,6 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } containedIds.clear(); generateByProfile(r, sd, r.root(), sd.getSnapshot().getElement(), ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), false, 0); - } catch (Exception e) { e.printStackTrace(); x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); @@ -121,6 +120,12 @@ public class ProfileDrivenRenderer extends ResourceRenderer { public String display(Resource r) throws UnsupportedEncodingException, IOException { return "todo"; } + + @Override + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + return "Not done yet"; + } + // // public void inject(Element er, XhtmlNode x, NarrativeStatus status, boolean pretty) { // if (!x.hasAttribute("xmlns")) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProvenanceRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProvenanceRenderer.java index 47e92a26b..cba48b21e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProvenanceRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProvenanceRenderer.java @@ -11,6 +11,7 @@ import org.hl7.fhir.r5.model.Reference; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.UriType; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class ProvenanceRenderer extends ResourceRenderer { @@ -19,7 +20,7 @@ public class ProvenanceRenderer extends ResourceRenderer { super(context); } - public boolean render(XhtmlNode x, DomainResource prv) throws UnsupportedEncodingException, IOException { + public boolean render(XhtmlNode x, Resource prv) throws UnsupportedEncodingException, IOException { return render(x, (Provenance) prv); } @@ -149,5 +150,10 @@ public class ProvenanceRenderer extends ResourceRenderer { public String display(Provenance prv) throws UnsupportedEncodingException, IOException { return "Provenance for "+displayReference(prv, prv.getTargetFirstRep()); } - + + @Override + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + return "Not done yet"; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java index a2f8bce9f..2237cc2ac 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java @@ -42,7 +42,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer { super(context); } - public boolean render(XhtmlNode x, DomainResource q) throws UnsupportedEncodingException, IOException { + public boolean render(XhtmlNode x, Resource q) throws UnsupportedEncodingException, IOException { return render(x, (Questionnaire) q); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireResponseRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireResponseRenderer.java index 4bd77b00c..0c5e8bbf0 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireResponseRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireResponseRenderer.java @@ -44,7 +44,7 @@ public class QuestionnaireResponseRenderer extends ResourceRenderer { super(context); } - public boolean render(XhtmlNode x, DomainResource q) throws UnsupportedEncodingException, IOException { + public boolean render(XhtmlNode x, Resource q) throws UnsupportedEncodingException, IOException { return render(x, (QuestionnaireResponse) q); } @@ -850,4 +850,9 @@ public class QuestionnaireResponseRenderer extends ResourceRenderer { return "todo"; } + @Override + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + return "Not done yet"; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java new file mode 100644 index 000000000..fc1a1867c --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java @@ -0,0 +1,23 @@ +package org.hl7.fhir.r5.renderers; + + +/** + * Rendering framework: + * + * * boolean render(DomainResource) : produce an HTML representation suitable for runtime / documentation, and insert it into the resource. Return true of any extensions encountered + * * boolean render(XhtmlNode, Resource: produce an HTML representation, and fill out the provided node with it. Return true of any extensions encountered + * * XhtmlNode build(DomainResource): same as render(DomainResource) but also return the XHtmlNode + * + * * String display(Base) : produce a plan text concise representation that serves to describe the resource + * * void display(XhtmlNode, Base) : produce a plan text concise representation that serves to describe the resource + * + * * void describe(XhtmlNode, Resource) : produce a short summary of the resource with key details presented (potentially more verbose than display, but still suitable for a single line) + * + * if not specific code for rendering a resource has been provided, and there's no liquid script to guide it, a generic rendering based onthe profile will be performed + * + * @author graha + * + */ +public class Renderer { + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java index a855e71da..8a90c2726 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java @@ -81,18 +81,23 @@ public abstract class ResourceRenderer extends DataRenderer { return x; } - public abstract boolean render(XhtmlNode x, DomainResource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome; + public abstract boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome; public boolean render(XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { ProfileDrivenRenderer pr = new ProfileDrivenRenderer(context); return pr.render(x, r); } - public void describe(XhtmlNode x, DomainResource r) throws UnsupportedEncodingException, IOException { + public void describe(XhtmlNode x, Resource r) throws UnsupportedEncodingException, IOException { + x.tx(display(r)); + } + + public void describe(XhtmlNode x, ResourceWrapper r) throws UnsupportedEncodingException, IOException { x.tx(display(r)); } public abstract String display(Resource r) throws UnsupportedEncodingException, IOException; + public abstract String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException; public static void inject(DomainResource r, XhtmlNode x, NarrativeStatus status) { if (!x.hasAttribute("xmlns")) @@ -152,7 +157,12 @@ public abstract class ResourceRenderer extends DataRenderer { new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), true, r.getReference().startsWith("#")); } } else if (tr != null && tr.getResource() != null) { - new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), r.getReference().startsWith("#"), r.getReference().startsWith("#")); + if (tr.getReference().startsWith("#")) { + // we already rendered this in this page + c.tx("See above ("+tr.getResource().fhirType()+"/"+tr.getResource().getId()+")"); + } else { + new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), r.getReference().startsWith("#"), r.getReference().startsWith("#")); + } } else { c.addText(r.getReference()); } @@ -215,7 +225,7 @@ public abstract class ResourceRenderer extends DataRenderer { } else { bundleUrl = "#" +fullUrlToAnchor(bundleElement.getChildValue("fullUrl")); } - return new ResourceWithReference(bundleUrl, new ResourceWrapperMetaElement(this.context, bundleElement)); + return new ResourceWithReference(bundleUrl, new ResourceWrapperMetaElement(this.context, br)); } } @@ -256,6 +266,14 @@ public abstract class ResourceRenderer extends DataRenderer { return null; } + protected PropertyWrapper getProperty(BaseWrapper res, String name) { + for (PropertyWrapper t : res.children()) { + if (t.getName().equals(name)) + return t; + } + return null; + } + protected boolean valued(PropertyWrapper pw) { return pw != null && pw.hasValues(); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java index 6a7250399..8e06f068d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java @@ -9,6 +9,7 @@ import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.utilities.xhtml.XhtmlNode; @@ -22,7 +23,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { super(context, rcontext); } - public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { return render(x, (StructureDefinition) dr); } @@ -44,4 +45,14 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return ((StructureDefinition) r).present(); } + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + if (r.has("title")) { + return r.children("title").get(0).getBase().primitiveValue(); + } + if (r.has("name")) { + return r.children("name").get(0).getBase().primitiveValue(); + } + return "??"; + } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java index 599bfb78e..46143644a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java @@ -23,6 +23,7 @@ import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; @@ -44,6 +45,16 @@ public abstract class TerminologyRenderer extends ResourceRenderer { return ((CanonicalResource) r).present(); } + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + if (r.has("title")) { + return r.children("title").get(0).getBase().primitiveValue(); + } + if (r.has("name")) { + return r.children("name").get(0).getBase().primitiveValue(); + } + return "??"; + } + protected class TargetElementComponentWrapper { protected ConceptMapGroupComponent group; protected TargetElementComponent comp; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java index 935a003a9..93f81735a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java @@ -59,7 +59,7 @@ public class ValueSetRenderer extends TerminologyRenderer { private List renderingMaps = new ArrayList(); - public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { return render(x, (ValueSet) dr, false); } From 5299bbe16b722a17616367729dca804d02a106c5 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 3 Aug 2020 17:40:21 +1000 Subject: [PATCH 7/9] * better validation of external references. Note: this is a potentially significant change: things that were called ok before may not be now, and things that were not ok before may become so, depending on the interplay between this and auto-load, further work may be needed here * Support better validation of version specific profiles in meta.profile. This may also find new errors that were not previously being found * Support auto-determination of the version of FHIR to use when using the java validator * auto-load packages from the package server when references to profiles etc are encountered * look for references inside other parameters in Parameters resource --- RELEASE_NOTES.md | 18 + .../fhir/r5/context/SimpleWorkerContext.java | 5 + .../hl7/fhir/r5/renderers/utils/Resolver.java | 95 +++-- .../hl7/fhir/utilities/VersionUtilities.java | 31 ++ .../hl7/fhir/utilities/cache/NpmPackage.java | 20 ++ .../fhir/utilities/i18n/I18nConstants.java | 5 + .../src/main/resources/Messages.properties | 4 + .../hl7/fhir/validation/ValidationEngine.java | 334 ++++++++++++++---- .../org/hl7/fhir/validation/Validator.java | 27 ++ .../fhir/validation/cli/model/CliContext.java | 2 +- .../services/StandAloneValidatorFetcher.java | 109 ++++++ .../cli/services/ValidationService.java | 11 + .../instance/InstanceValidator.java | 97 ++++- 13 files changed, 653 insertions(+), 105 deletions(-) create mode 100644 org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e69de29bb..1fea49ef9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -0,0 +1,18 @@ +Validator Fixes: +* Support auto-determination of the version of FHIR to use when using the java validator +* auto-load packages from the package server when references to profiles etc are encountered +* Support better validation of version specific profiles in meta.profile +* look for references inside other parameters in Parameters resource + +Other Code changes: +* Rendering: add rendering for Parameters resources +* Rendering: refactor of resource resolution code to support Parameters +* General clean up of rendering consistency & implement additional details when rendering (including patient summary) +* Rendering: major overhaul of DiagnosticReport rendering +* Fix NPE bug in value set comparison + + +TODO before commit: +* check version of contained resources +* review validation of CanonicalResource.url + diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java index a04fb8a1f..3a87b4ff1 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java @@ -267,6 +267,11 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon return res; } + public static SimpleWorkerContext fromNothing() throws FileNotFoundException, FHIRException, IOException { + SimpleWorkerContext res = new SimpleWorkerContext(); + return res; + } + private void loadDefinitionItem(String name, InputStream stream, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException { if (name.endsWith(".xml")) loadFromFile(stream, name, loader, filter); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/Resolver.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/Resolver.java index c39b654a7..94c413b31 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/Resolver.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/Resolver.java @@ -3,46 +3,58 @@ package org.hl7.fhir.r5.renderers.utils; import org.hl7.fhir.r5.model.Bundle; import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r5.model.DomainResource; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; +import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContextType; import org.w3c.dom.Element; public class Resolver { + public enum ResourceContextType { + PARAMETERS, BUNDLE + } + public interface IReferenceResolver { ResourceWithReference resolve(RenderingContext context, String url); } public static class ResourceContext { - Bundle bundleResource; - org.hl7.fhir.r5.elementmodel.Element bundleElement; + private ResourceContextType type; + private Resource containerResource; + private org.hl7.fhir.r5.elementmodel.Element containerElement; DomainResource resourceResource; org.hl7.fhir.r5.elementmodel.Element resourceElement; - public ResourceContext(Bundle bundle, DomainResource dr) { + public ResourceContext(ResourceContextType type, Resource bundle, DomainResource dr) { super(); - this.bundleResource = bundle; + this.type = type; + this.containerResource = bundle; this.resourceResource = dr; } - public ResourceContext(org.hl7.fhir.r5.elementmodel.Element bundle, org.hl7.fhir.r5.elementmodel.Element dr) { - this.bundleElement = bundle; + public ResourceContext(ResourceContextType type, org.hl7.fhir.r5.elementmodel.Element bundle, org.hl7.fhir.r5.elementmodel.Element dr) { + super(); + this.type = type; + this.containerElement = bundle; this.resourceElement = dr; } - public ResourceContext(Object bundle, Element doc) { - // TODO Auto-generated constructor stub - } +// public ResourceContext(Object bundle, Element doc) { +// // TODO Auto-generated constructor stub +// } - public Bundle getBundleResource() { - return bundleResource; - } +// public Bundle getBundleResource() { +// return containerResource; +// } - public org.hl7.fhir.r5.elementmodel.Element getBundleElement() { - return bundleElement; - } + // public org.hl7.fhir.r5.elementmodel.Element getBundleElement() { +// return containerElement; +// } +// public DomainResource getResourceResource() { return resourceResource; } @@ -64,13 +76,27 @@ public class Resolver { } return null; } - if (bundleResource != null) { - for (BundleEntryComponent be : bundleResource.getEntry()) { - if (be.getFullUrl().equals(value)) - return be; - if (value.equals(be.getResource().fhirType()+"/"+be.getResource().getId())) - return be; - } + if (type == ResourceContextType.BUNDLE) { + if (containerResource != null) { + for (BundleEntryComponent be : ((Bundle) containerResource).getEntry()) { + if (be.getFullUrl().equals(value)) + return be; + if (value.equals(be.getResource().fhirType()+"/"+be.getResource().getId())) + return be; + } + } + } + if (type == ResourceContextType.PARAMETERS) { + if (containerResource != null) { + for (ParametersParameterComponent p : ((Parameters) containerResource).getParameter()) { + if (p.getResource() != null && value.equals(p.getResource().fhirType()+"/"+p.getResource().getId())) { + BundleEntryComponent be = new BundleEntryComponent(); + be.setResource(p.getResource()); + return be; + + } + } + } } return null; } @@ -85,13 +111,24 @@ public class Resolver { } return null; } - if (bundleElement != null) { - for (org.hl7.fhir.r5.elementmodel.Element be : bundleElement.getChildren("entry")) { - org.hl7.fhir.r5.elementmodel.Element res = be.getNamedChild("resource"); - if (value.equals(be.getChildValue("fullUrl"))) - return be; - if (value.equals(res.fhirType()+"/"+res.getChildValue("id"))) - return be; + if (type == ResourceContextType.BUNDLE) { + if (containerElement != null) { + for (org.hl7.fhir.r5.elementmodel.Element be : containerElement.getChildren("entry")) { + org.hl7.fhir.r5.elementmodel.Element res = be.getNamedChild("resource"); + if (value.equals(be.getChildValue("fullUrl"))) + return be; + if (value.equals(res.fhirType()+"/"+res.getChildValue("id"))) + return be; + } + } + } + if (type == ResourceContextType.PARAMETERS) { + if (containerElement != null) { + for (org.hl7.fhir.r5.elementmodel.Element p : containerElement.getChildren("parameter")) { + org.hl7.fhir.r5.elementmodel.Element res = p.getNamedChild("resource"); + if (value.equals(res.fhirType()+"/"+res.getChildValue("id"))) + return p; + } } } return null; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java index 8201d97a9..544f83079 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java @@ -1,6 +1,7 @@ package org.hl7.fhir.utilities; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.utilities.cache.NpmPackage; /* Copyright (c) 2011+, HL7, Inc. @@ -35,6 +36,22 @@ import org.hl7.fhir.exceptions.FHIRException; public class VersionUtilities { + public static class VersionURLInfo { + private String version; + private String url; + public VersionURLInfo(String version, String url) { + super(); + this.version = version; + this.url = url; + } + public String getVersion() { + return version; + } + public String getUrl() { + return url; + } + } + public static final String CURRENT_VERSION = "4.4"; public static final String CURRENT_FULL_VERSION = "4.4.0"; @@ -225,4 +242,18 @@ public class VersionUtilities { throw new FHIRException("Unknown version "+version); } + public static VersionURLInfo parseVersionUrl(String url) { + if (url.length() < 24) { + return null; + } + String v = url.substring(20, 24); + if (v.endsWith("/")) { + v = v.substring(0, v.length()-1); + if (Utilities.existsInList(v, "1.0", "1.4", "3.0", "4.0", "5.0", CURRENT_VERSION)) { + return new VersionURLInfo(v, "http://hl7.org/fhir/"+url.substring(24)); + } + } + return null; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java index 166961e17..334ab7d4d 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java @@ -1057,6 +1057,26 @@ public class NpmPackage { public boolean isCore() { return "fhir.core".equals(JSONUtil.str(npm, "type")); } + + public boolean hasCanonical(String url) { + if (url == null) { + return false; + } + String u = url.contains("|") ? url.substring(0, url.indexOf("|")) : url; + String v = url.contains("|") ? url.substring(url.indexOf("|")+1) : null; + NpmPackageFolder folder = folders.get("package"); + if (folder != null) { + for (JsonElement e : folder.index.getAsJsonArray("files")) { + JsonObject o = (JsonObject) e; + if (u.equals(JSONUtil.str(o, "url"))) { + if (v == null || v.equals(JSONUtil.str(o, "version"))) { + return true; + } + } + } + } + return false; + } } \ No newline at end of file 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 34b8cc05c..c70ce1748 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 @@ -25,6 +25,7 @@ public class I18nConstants { public static final String BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE = "Bundle_BUNDLE_Entry_NoFirstResource"; public static final String BUNDLE_BUNDLE_ENTRY_NOFULLURL = "Bundle_BUNDLE_Entry_NoFullUrl"; public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE = "Bundle_BUNDLE_Entry_NoProfile"; + public static final String BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES = "BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES"; public static final String BUNDLE_BUNDLE_ENTRY_NOTFOUND = "Bundle_BUNDLE_Entry_NotFound"; public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN = "Bundle_BUNDLE_Entry_Orphan"; public static final String BUNDLE_BUNDLE_ENTRY_TYPE = "Bundle_BUNDLE_Entry_Type"; @@ -543,10 +544,14 @@ public class I18nConstants { public static final String VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL = "VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL"; public static final String VALIDATION_VAL_PROFILE_SIGNPOST_META = "VALIDATION_VAL_PROFILE_SIGNPOST_META"; public static final String VALIDATION_VAL_PROFILE_SLICEORDER = "Validation_VAL_Profile_SliceOrder"; + public static final String VALIDATION_VAL_PROFILE_OTHER_VERSION = "VALIDATION_VAL_PROFILE_OTHER_VERSION"; + public static final String VALIDATION_VAL_PROFILE_THIS_VERSION_OK = "VALIDATION_VAL_PROFILE_THIS_VERSION_OK"; + public static final String VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER = "VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER"; public static final String VALIDATION_VAL_PROFILE_UNKNOWN = "Validation_VAL_Profile_Unknown"; public static final String VALIDATION_VAL_PROFILE_WRONGTYPE = "Validation_VAL_Profile_WrongType"; public static final String VALIDATION_VAL_PROFILE_WRONGTYPE2 = "Validation_VAL_Profile_WrongType2"; public static final String VALIDATION_VAL_UNKNOWN_PROFILE = "Validation_VAL_Unknown_Profile"; + public static final String VALUESET_INCLUDE_INVALID_CONCEPT_CODE = "VALUESET_INCLUDE_INVALID_CONCEPT_CODE"; public static final String VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER = "VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER"; public static final String VALUESET_NO_SYSTEM_WARNING = "VALUESET_NO_SYSTEM_WARNING"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index d49720b3c..8bd7307cf 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -571,3 +571,7 @@ PACKAGE_VERSION_MISMATCH = FHIR Version mismatch in package {0}: version is {2} VALUESET_REFERENCE_UNKNOWN = The value set import {0} could not be found so cannot be checked VALUESET_REFERENCE_INVALID_TYPE = The value set import {0} points to a resource of type {1} which is not valid SD_MUST_HAVE_DERIVATION = StructureDefinition {0} must have a derivation, since it has a baseDefinition +VALIDATION_VAL_PROFILE_OTHER_VERSION = Profile is for a different version of FHIR ({0}) so has been ignored +VALIDATION_VAL_PROFILE_THIS_VERSION_OK = Profile for this version of FHIR - all OK +VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER = Profile is for this version of FHIR, but is an invalid type {0} +BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES = Multiple profiles found for contained resource. This is not supported at this time. (Type {0}: {1}) 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 78ac32bb0..e02dac771 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 @@ -34,6 +34,7 @@ import org.hl7.fhir.r5.utils.*; import org.hl7.fhir.r5.utils.IResourceValidator.*; import org.hl7.fhir.r5.utils.StructureMapUtilities.ITransformerServices; import org.hl7.fhir.utilities.i18n.I18nConstants; +import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher.IPackageInstaller; import org.hl7.fhir.validation.instance.InstanceValidator; import org.hl7.fhir.utilities.IniFile; import org.hl7.fhir.utilities.TextFile; @@ -163,7 +164,42 @@ POSSIBILITY OF SUCH DAMAGE. * @author Grahame Grieve * */ -public class ValidationEngine implements IValidatorResourceFetcher { +public class ValidationEngine implements IValidatorResourceFetcher, IPackageInstaller { + + public static class VersionSourceInformation { + + private List report = new ArrayList<>(); + private List versions = new ArrayList<>(); + + public void see(String version, String src) { + version = VersionUtilities.getMajMin(version); + report.add(src+": "+version); + if (!versions.contains(version)) { + versions.add(version); + Collections.sort(versions); + } + } + + public boolean isEmpty() { + return versions.isEmpty(); + } + + public int size() { + return versions.size(); + } + + public String version() { + return versions.get(0); + } + + public List getReport() { + if (report.isEmpty()) { + report.add("(nothing found)"); + } + return report; + } + + } public class ScanOutputItem { private String ref; @@ -311,9 +347,10 @@ public class ValidationEngine implements IValidatorResourceFetcher { public ValidationEngine() throws IOException { pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); + context = SimpleWorkerContext.fromNothing(); } - public void setTerminologyServer(String src, String log, FhirPublication version) throws Exception { + public void setTerminologyServer(String src, String log, FhirPublication version) throws FHIRException, URISyntaxException { connectToTSServer(src, log, version); } @@ -342,7 +379,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { this.showTimes = showTimes; } - public ValidationEngine(String src, String txsrvr, String txLog, FhirPublication version, boolean canRunWithoutTerminologyServer, String vString) throws Exception { + public ValidationEngine(String src, String txsrvr, String txLog, FhirPublication version, boolean canRunWithoutTerminologyServer, String vString) throws FHIRException, IOException, URISyntaxException { pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); loadCoreDefinitions(src, false); context.setCanRunWithoutTerminology(canRunWithoutTerminologyServer); @@ -350,14 +387,14 @@ public class ValidationEngine implements IValidatorResourceFetcher { this.version = vString; } - public ValidationEngine(String src, String txsrvr, String txLog, FhirPublication version, String vString) throws Exception { + public ValidationEngine(String src, String txsrvr, String txLog, FhirPublication version, String vString) throws FHIRException, IOException, URISyntaxException { pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); loadCoreDefinitions(src, false); setTerminologyServer(txsrvr, txLog, version); this.version = vString; } - public ValidationEngine(String src) throws Exception { + public ValidationEngine(String src) throws FHIRException, IOException { loadCoreDefinitions(src, false); pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); } @@ -370,7 +407,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { this.language = language; } - private void loadCoreDefinitions(String src, boolean recursive) throws Exception { + private void loadCoreDefinitions(String src, boolean recursive) throws FHIRException, IOException { if (pcm == null) { pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); } @@ -435,7 +472,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { return ep; } - private byte[] loadProfileSource(String src) throws Exception { + private byte[] loadProfileSource(String src) throws FHIRException, FileNotFoundException, IOException { if (Utilities.noString(src)) { throw new FHIRException("Profile Source '" + src + "' could not be processed"); } else if (src.startsWith("https:") || src.startsWith("http:")) { @@ -447,13 +484,13 @@ public class ValidationEngine implements IValidatorResourceFetcher { } } - private byte[] loadProfileFromUrl(String src) throws Exception { + private byte[] loadProfileFromUrl(String src) throws FHIRException { try { URL url = new URL(src+"?nocache=" + System.currentTimeMillis()); URLConnection c = url.openConnection(); return IOUtils.toByteArray(c.getInputStream()); } catch (Exception e) { - throw new Exception("Unable to find definitions at URL '"+src+"': "+e.getMessage(), e); + throw new FHIRException("Unable to find definitions at URL '"+src+"': "+e.getMessage(), e); } } @@ -464,9 +501,10 @@ public class ValidationEngine implements IValidatorResourceFetcher { return TextFile.fileToBytes(src); } - /** explore should be true if we're trying to load an -ig parameter, and false if we're loading source **/ + /** explore should be true if we're trying to load an -ig parameter, and false if we're loading source + * @throws IOException **/ - private Map loadIgSource(String src, boolean recursive, boolean explore) throws Exception { + private Map loadIgSource(String src, boolean recursive, boolean explore) throws FHIRException, IOException { // src can be one of the following: // - a canonical url for an ig - this will be converted to a package id and loaded into the cache // - a package id for an ig - this will be loaded into the cache @@ -510,11 +548,60 @@ public class ValidationEngine implements IValidatorResourceFetcher { } else if ((src.matches(FilesystemPackageCacheManager.PACKAGE_REGEX) || src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX)) && !src.endsWith(".zip") && !src.endsWith(".tgz")) { return fetchByPackage(src); } - throw new Exception("Unable to find/resolve/read -ig "+src); + throw new FHIRException("Unable to find/resolve/read -ig "+src); + } + + private Map loadIgSourceForVersion(String src, boolean recursive, boolean explore, VersionSourceInformation versions) throws FHIRException, IOException { + if (src.startsWith("https:") || src.startsWith("http:")) { + String v = null; + if (src.contains("|")) { + v = src.substring(src.indexOf("|")+1); + src = src.substring(0, src.indexOf("|")); + } + String pid = pcm.getPackageId(src); + if (!Utilities.noString(pid)) { + versions.see(fetchVersionByPackage(pid+(v == null ? "" : "#"+v)), "Package "+src); + return null; + } else { + return fetchVersionFromUrl(src+(v == null ? "" : "|"+v), explore, versions); + } + } + + File f = new File(Utilities.path(src)); + if (f.exists()) { + if (f.isDirectory() && new File(Utilities.path(src, "package.tgz")).exists()) { + versions.see(loadPackageForVersion(new FileInputStream(Utilities.path(src, "package.tgz")), Utilities.path(src, "package.tgz")), "Package "+src); + return null; + } + if (f.isDirectory() && new File(Utilities.path(src, "igpack.zip")).exists()) + return readZip(new FileInputStream(Utilities.path(src, "igpack.zip"))); + if (f.isDirectory() && new File(Utilities.path(src, "validator.pack")).exists()) + return readZip(new FileInputStream(Utilities.path(src, "validator.pack"))); + if (f.isDirectory()) + return scanDirectory(f, recursive); + if (src.endsWith(".tgz")) { + versions.see(loadPackageForVersion(new FileInputStream(src), src), "Package "+src); + return null; + } + if (src.endsWith(".pack")) + return readZip(new FileInputStream(src)); + if (src.endsWith("igpack.zip")) + return readZip(new FileInputStream(src)); + FhirFormat fmt = checkIsResource(src); + if (fmt != null) { + Map res = new HashMap(); + res.put(Utilities.changeFileExt(src, "."+fmt.getExtension()), TextFile.fileToBytesNCS(src)); + return res; + } + } else if ((src.matches(FilesystemPackageCacheManager.PACKAGE_REGEX) || src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX)) && !src.endsWith(".zip") && !src.endsWith(".tgz")) { + versions.see(fetchVersionByPackage(src), "Package "+src); + return null; + } + throw new FHIRException("Unable to find/resolve/read -ig "+src); } - private Map fetchFromUrl(String src, boolean explore) throws Exception { + private Map fetchFromUrl(String src, boolean explore) throws FHIRException, IOException { if (src.endsWith(".tgz")) return loadPackage(fetchFromUrlSpecific(src, false), src); if (src.endsWith(".pack")) @@ -551,15 +638,59 @@ public class ValidationEngine implements IValidatorResourceFetcher { res.put(Utilities.changeFileExt(src, "."+fmt.getExtension()), cnt); return res; } - throw new Exception("Unable to find/resolve/read -ig "+src); + throw new FHIRException("Unable to find/resolve/read -ig "+src); } - private InputStream fetchFromUrlSpecific(String source, boolean optional) throws Exception { + private Map fetchVersionFromUrl(String src, boolean explore, VersionSourceInformation versions) throws FHIRException, IOException { + if (src.endsWith(".tgz")) { + versions.see(loadPackageForVersion(fetchFromUrlSpecific(src, false), src), "From Package "+src); + return null; + } + if (src.endsWith(".pack")) + return readZip(fetchFromUrlSpecific(src, false)); + if (src.endsWith("igpack.zip")) + return readZip(fetchFromUrlSpecific(src, false)); + + InputStream stream = null; + if (explore) { + stream = fetchFromUrlSpecific(Utilities.pathURL(src, "package.tgz"), true); + if (stream != null) { + versions.see(loadPackageForVersion(stream, Utilities.pathURL(src, "package.tgz")), "From Package at "+src); + return null; + } + // todo: these options are deprecated - remove once all IGs have been rebuilt post R4 technical correction + stream = fetchFromUrlSpecific(Utilities.pathURL(src, "igpack.zip"), true); + if (stream != null) + return readZip(stream); + stream = fetchFromUrlSpecific(Utilities.pathURL(src, "validator.pack"), true); + if (stream != null) + return readZip(stream); + stream = fetchFromUrlSpecific(Utilities.pathURL(src, "validator.pack"), true); + //// ----- + } + + // ok, having tried all that... now we'll just try to access it directly + byte[] cnt; + if (stream == null) + cnt = fetchFromUrlSpecific(src, "application/json", true); + else + cnt = TextFile.streamToBytes(stream); + + FhirFormat fmt = checkIsResource(cnt, src); + if (fmt != null) { + Map res = new HashMap(); + res.put(Utilities.changeFileExt(src, "."+fmt.getExtension()), cnt); + return res; + } + throw new FHIRException("Unable to find/resolve/read -ig "+src); + } + + private InputStream fetchFromUrlSpecific(String source, boolean optional) throws FHIRException, IOException { try { URL url = new URL(source+"?nocache=" + System.currentTimeMillis()); URLConnection c = url.openConnection(); return c.getInputStream(); - } catch (Exception e) { + } catch (IOException e) { if (optional) return null; else @@ -567,13 +698,13 @@ public class ValidationEngine implements IValidatorResourceFetcher { } } - private byte[] fetchFromUrlSpecific(String source, String contentType, boolean optional) throws Exception { + private byte[] fetchFromUrlSpecific(String source, String contentType, boolean optional) throws FHIRException, IOException { try { URL url = new URL(source+"?nocache=" + System.currentTimeMillis()); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestProperty("Accept", contentType); return TextFile.streamToBytes(conn.getInputStream()); - } catch (Exception e) { + } catch (IOException e) { if (optional) return null; else @@ -604,11 +735,15 @@ public class ValidationEngine implements IValidatorResourceFetcher { return Utilities.existsInList(Utilities.getFileExtension(ff.getName()).toLowerCase(), "md", "css", "js", "png", "gif", "jpg", "html", "tgz", "pack", "zip"); } - private Map loadPackage(InputStream stream, String name) throws Exception { + private Map loadPackage(InputStream stream, String name) throws FHIRException, IOException { return loadPackage(NpmPackage.fromPackage(stream)); } - public Map loadPackage(NpmPackage pi) throws Exception { + private String loadPackageForVersion(InputStream stream, String name) throws FHIRException, IOException { + return NpmPackage.fromPackage(stream).fhirVersion(); + } + + public Map loadPackage(NpmPackage pi) throws FHIRException, IOException { context.getLoadedPackages().add(pi.name()+"#"+pi.version()); Map res = new HashMap(); for (String s : pi.dependencies()) { @@ -652,7 +787,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { System.out.println(message); } - private Map fetchByPackage(String src) throws Exception { + private Map fetchByPackage(String src) throws FHIRException, IOException { String id = src; String version = null; if (src.contains("#")) { @@ -679,13 +814,46 @@ public class ValidationEngine implements IValidatorResourceFetcher { return loadPackage(pi); } - private Map resolvePackage(String id, String v) throws Exception { + private String fetchVersionByPackage(String src) throws FHIRException, IOException { + String id = src; + String version = null; + if (src.contains("#")) { + id = src.substring(0, src.indexOf("#")); + version = src.substring(src.indexOf("#")+1); + } + if (pcm == null) { + log("Creating Package manager?"); + pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); + } + if (version == null) { + version = pcm.getLatestVersion(id); + } + NpmPackage pi = null; + if (version == null) { + pi = pcm.loadPackageFromCacheOnly(id); + if (pi != null) + log(" ... Using version "+pi.version()); + } else + pi = pcm.loadPackageFromCacheOnly(id, version); + if (pi == null) { + return resolvePackageForVersion(id, version); + } else { + return pi.fhirVersion(); + } + } + + private Map resolvePackage(String id, String v) throws FHIRException, IOException { NpmPackage pi = pcm.loadPackage(id, v); if (pi != null && v == null) log(" ... Using version "+pi.version()); return loadPackage(pi); } + private String resolvePackageForVersion(String id, String v) throws FHIRException, IOException { + NpmPackage pi = pcm.loadPackage(id, v); + return pi.fhirVersion(); + } + public SimpleWorkerContext getContext() { return context; } @@ -758,7 +926,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { } } - public void loadProfile(String src) throws Exception { + public void loadProfile(String src) throws FHIRException, IOException { if (context.hasResource(StructureDefinition.class, src)) return; if (context.hasResource(ImplementationGuide.class, src)) @@ -770,7 +938,13 @@ public class ValidationEngine implements IValidatorResourceFetcher { context.cacheResource(r); } - public void loadIg(String src, boolean recursive) throws IOException, FHIRException, Exception { + public void scanForIgVersion(String src, boolean recursive, VersionSourceInformation versions) throws IOException, FHIRException, Exception { + Map source = loadIgSourceForVersion(src, recursive, true, versions); + if (source != null && source.containsKey("version.info")) + versions.see(readInfoVersion(source.get("version.info")), "version.info in "+src); + } + + public void loadIg(String src, boolean recursive) throws IOException, FHIRException { NpmPackage npm = src.matches(FilesystemPackageCacheManager.PACKAGE_REGEX) ? pcm.loadPackage(src, null) : null; if (npm != null) { for (String s : npm.dependencies()) { @@ -834,7 +1008,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { return r; } - public Resource loadResourceByVersion(String version, byte[] content, String fn) throws IOException, Exception { + public Resource loadResourceByVersion(String version, byte[] content, String fn) throws IOException, FHIRException { Resource r; if (version.startsWith("3.0")) { org.hl7.fhir.dstu3.model.Resource res; @@ -845,7 +1019,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { else if (fn.endsWith(".txt") || fn.endsWith(".map") ) res = new org.hl7.fhir.dstu3.utils.StructureMapUtilities(null).parse(new String(content)); else - throw new Exception("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for "+fn); r = VersionConvertor_30_50.convertResource(res, false); } else if (version.startsWith("4.0")) { org.hl7.fhir.r4.model.Resource res; @@ -856,7 +1030,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { else if (fn.endsWith(".txt") || fn.endsWith(".map") ) res = new org.hl7.fhir.r4.utils.StructureMapUtilities(null).parse(new String(content), fn); else - throw new Exception("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for "+fn); r = VersionConvertor_40_50.convertResource(res); } else if (version.startsWith("1.4")) { org.hl7.fhir.dstu2016may.model.Resource res; @@ -865,7 +1039,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { else if (fn.endsWith(".json") && !fn.endsWith("template.json")) res = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(new ByteArrayInputStream(content)); else - throw new Exception("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for "+fn); r = VersionConvertor_14_50.convertResource(res); } else if (version.startsWith("1.0")) { org.hl7.fhir.dstu2.model.Resource res; @@ -874,7 +1048,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { else if (fn.endsWith(".json") && !fn.endsWith("template.json")) res = new org.hl7.fhir.dstu2.formats.JsonParser().parse(new ByteArrayInputStream(content)); else - throw new Exception("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for "+fn); VersionConvertorAdvisor50 advisor = new org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor5(); r = VersionConvertor_10_50.convertResource(res, advisor); } else if (version.equals(Constants.VERSION) || "current".equals(version)) { @@ -887,9 +1061,9 @@ public class ValidationEngine implements IValidatorResourceFetcher { else if (fn.endsWith(".txt") || fn.endsWith(".map") ) r = new org.hl7.fhir.r5.utils.StructureMapUtilities(null).parse(new String(content), fn); else - throw new Exception("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for "+fn); } else - throw new Exception("Unsupported version "+version); + throw new FHIRException("Unsupported version "+version); return r; } @@ -923,11 +1097,11 @@ public class ValidationEngine implements IValidatorResourceFetcher { FhirFormat cntType = null; } - public Content loadContent(String source, String opName) throws Exception { + public Content loadContent(String source, String opName) throws FHIRException, IOException { Map s = loadIgSource(source, false, false); Content res = new Content(); if (s.size() != 1) - throw new Exception("Unable to find resource " + source + " to "+opName); + throw new FHIRException("Unable to find resource " + source + " to "+opName); for (Entry t: s.entrySet()) { res.focus = t.getValue(); if (t.getKey().endsWith(".json")) @@ -939,13 +1113,13 @@ public class ValidationEngine implements IValidatorResourceFetcher { else if (t.getKey().endsWith(".txt") || t.getKey().endsWith(".map")) res.cntType = FhirFormat.TEXT; else - throw new Exception("Todo: Determining resource type is not yet done"); + throw new FHIRException("Todo: Determining resource type is not yet done"); } return res; } // testing entry point - public OperationOutcome validate(FhirFormat format, InputStream stream, List profiles) throws Exception { + public OperationOutcome validate(FhirFormat format, InputStream stream, List profiles) throws FHIRException, IOException, EOperationOutcome { List messages = new ArrayList(); InstanceValidator validator = getValidator(); validator.validate(null, messages, stream, format, asSdList(profiles)); @@ -966,13 +1140,13 @@ public class ValidationEngine implements IValidatorResourceFetcher { return list; } - public OperationOutcome validate(String source, List profiles) throws Exception { + public OperationOutcome validate(String source, List profiles) throws FHIRException, IOException { List l = new ArrayList(); l.add(source); return (OperationOutcome)validate(l, profiles); } - public List validateScan(List sources, Set guides) throws Exception { + public List validateScan(List sources, Set guides) throws FHIRException, IOException, EOperationOutcome { List refs = new ArrayList(); handleSources(sources, refs); @@ -1048,7 +1222,28 @@ public class ValidationEngine implements IValidatorResourceFetcher { return null; } - public Resource validate(List sources, List profiles) throws Exception { + public void scanForVersions(List sources, VersionSourceInformation versions) throws FHIRException, IOException { + List refs = new ArrayList(); + handleSources(sources, refs); + for (String ref : refs) { + Content cnt = loadContent(ref, "validate"); + String s = TextFile.bytesToString(cnt.focus); + if (s.contains("http://hl7.org/fhir/3.0")) { + versions.see("3.0", "Profile in "+ref); + } + if (s.contains("http://hl7.org/fhir/1.0")) { + versions.see("1.0", "Profile in "+ref); + } + if (s.contains("http://hl7.org/fhir/4.0")) { + versions.see("4.0", "Profile in "+ref); + } + if (s.contains("http://hl7.org/fhir/1.4")) { + versions.see("1.4", "Profile in "+ref); + } + } + } + + public Resource validate(List sources, List profiles) throws FHIRException, IOException { List refs = new ArrayList(); boolean asBundle = handleSources(sources, refs); Bundle results = new Bundle(); @@ -1065,7 +1260,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { results.addEntry().setResource(outcome); } catch (Exception e) { System.out.println("Validation Infrastructure fail validating "+ref+": "+e.getMessage()); - throw e; + throw new FHIRException(e); } } if (asBundle) @@ -1082,7 +1277,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { } } - public OperationOutcome validateString(String location, String source, FhirFormat format, List profiles) throws Exception { + public OperationOutcome validateString(String location, String source, FhirFormat format, List profiles) throws FHIRException, IOException, EOperationOutcome, SAXException { return validate(location, source.getBytes(), format, profiles); } @@ -1136,14 +1331,14 @@ public class ValidationEngine implements IValidatorResourceFetcher { return isBundle; } - public OperationOutcome validate(byte[] source, FhirFormat cntType, List profiles) throws Exception { + public OperationOutcome validate(byte[] source, FhirFormat cntType, List profiles) throws FHIRException, IOException, EOperationOutcome { List messages = new ArrayList(); InstanceValidator validator = getValidator(); validator.validate(null, messages, new ByteArrayInputStream(source), cntType, asSdList(profiles)); return messagesToOutcome(messages); } - public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List profiles) throws Exception { + public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List profiles) throws FHIRException, IOException, EOperationOutcome, SAXException { List messages = new ArrayList(); if (doNative) { if (cntType == FhirFormat.JSON) @@ -1161,7 +1356,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { return messagesToOutcome(messages); } - public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List profiles, IdStatus resourceIdRule, boolean anyExtensionsAllowed, BestPracticeWarningLevel bpWarnings, CheckDisplayOption displayOption) throws Exception { + public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List profiles, IdStatus resourceIdRule, boolean anyExtensionsAllowed, BestPracticeWarningLevel bpWarnings, CheckDisplayOption displayOption) throws FHIRException, IOException, EOperationOutcome, SAXException { List messages = new ArrayList(); if (doNative) { if (cntType == FhirFormat.JSON) @@ -1254,12 +1449,12 @@ public class ValidationEngine implements IValidatorResourceFetcher { return issue.getSeverity().toString()+" @ "+issue.getLocation() + " " +issue.getDetails().getText() +(source != null ? " (src = "+source+")" : ""); } - public org.hl7.fhir.r5.elementmodel.Element transform(String source, String map) throws Exception { + public org.hl7.fhir.r5.elementmodel.Element transform(String source, String map) throws FHIRException, IOException { Content cnt = loadContent(source, "validate"); return transform(cnt.focus, cnt.cntType, map); } - public org.hl7.fhir.r5.elementmodel.Element transform(byte[] source, FhirFormat cntType, String mapUri) throws Exception { + 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)); @@ -1299,7 +1494,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { return Manager.build(getContext(), structureDefinition); } - public DomainResource generate(String source, String version) throws Exception { + public DomainResource generate(String source, String version) throws FHIRException, IOException, EOperationOutcome { Content cnt = loadContent(source, "validate"); Resource res = loadResourceByVersion(version, cnt.focus, source); RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); @@ -1307,25 +1502,25 @@ public class ValidationEngine implements IValidatorResourceFetcher { return (DomainResource) res; } - public void convert(String source, String output) throws Exception { + public void convert(String source, String output) throws FHIRException, IOException { Content cnt = loadContent(source, "validate"); Element e = Manager.parse(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 Exception { + public String evaluateFhirPath(String source, String expression) throws FHIRException, IOException { Content cnt = loadContent(source, "validate"); FHIRPathEngine fpe = new FHIRPathEngine(context); Element e = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType); return fpe.evaluateToString(e, expression); } - public StructureDefinition snapshot(String source, String version) throws Exception { + public StructureDefinition snapshot(String source, String version) throws FHIRException, IOException { Content cnt = loadContent(source, "validate"); Resource res = loadResourceByVersion(version, cnt.focus, Utilities.getFileNameForName(source)); if (!(res instanceof StructureDefinition)) - throw new Exception("Require a StructureDefinition for generating a snapshot"); + throw new FHIRException("Require a StructureDefinition for generating a snapshot"); StructureDefinition sd = (StructureDefinition) res; StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); @@ -1670,8 +1865,9 @@ public class ValidationEngine implements IValidatorResourceFetcher { @Override public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException { - if (!url.startsWith("http://hl7.org/fhir")) - return true; // we don't bother with those. + if (!url.startsWith("http://") && !url.startsWith("https://")) { // ignore these + return true; + } if (context.fetchResource(Resource.class, url) != null) return true; if (Utilities.existsInList(url, "http://hl7.org/fhir/sid/us-ssn", "http://hl7.org/fhir/sid/cvx", "http://hl7.org/fhir/sid/ndc", "http://hl7.org/fhir/sid/us-npi", "http://hl7.org/fhir/sid/icd-10", @@ -1679,6 +1875,9 @@ public class ValidationEngine implements IValidatorResourceFetcher { "http://hl7.org/fhir/workflow", "http://hl7.org/fhir/ConsentPolicy/opt-out", "http://hl7.org/fhir/ConsentPolicy/opt-in")) { return true; } + if (Utilities.existsInList(url, "http://loinc.org", "http://unitsofmeasure.org", "http://snomed.info/sct")) { + return true; + } if (fetcher != null) { return fetcher.resolveURL(appContext, path, url); }; @@ -1690,7 +1889,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { this.locale = locale; } - public void handleOutput(Resource r, String output, String version) throws Exception { + public void handleOutput(Resource r, String output, String version) throws FHIRException, IOException { if (output.startsWith("http://") || output.startsWith("http://")) { ByteArrayOutputStream bs = new ByteArrayOutputStream(); handleOutputToStream(r, output, bs, version); @@ -1719,7 +1918,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { } } - private void handleOutputToStream(Resource r, String fn, OutputStream s, String version) throws Exception { + private void handleOutputToStream(Resource r, String fn, OutputStream s, String version) throws FHIRException, IOException { if (fn.endsWith(".html") || fn.endsWith(".htm") && r instanceof DomainResource) new XhtmlComposer(XhtmlComposer.HTML, true).compose(s, ((DomainResource) r).getText().getDiv()); else if (version.startsWith("3.0")) { @@ -1731,7 +1930,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { else if (fn.endsWith(".txt") || fn.endsWith(".map") ) TextFile.stringToStream(org.hl7.fhir.dstu3.utils.StructureMapUtilities.render((org.hl7.fhir.dstu3.model.StructureMap) res), s, false); else - throw new Exception("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for "+fn); } else if (version.startsWith("4.0")) { org.hl7.fhir.r4.model.Resource res = VersionConvertor_40_50.convertResource(r); if (fn.endsWith(".xml") && !fn.endsWith("template.xml")) @@ -1741,7 +1940,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { else if (fn.endsWith(".txt") || fn.endsWith(".map") ) TextFile.stringToStream(org.hl7.fhir.r4.utils.StructureMapUtilities.render((org.hl7.fhir.r4.model.StructureMap) res), s, false); else - throw new Exception("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for "+fn); } else if (version.startsWith("1.4")) { org.hl7.fhir.dstu2016may.model.Resource res = VersionConvertor_14_50.convertResource(r); if (fn.endsWith(".xml") && !fn.endsWith("template.xml")) @@ -1749,7 +1948,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { else if (fn.endsWith(".json") && !fn.endsWith("template.json")) new org.hl7.fhir.dstu2016may.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu2016may.formats.IParser.OutputStyle.PRETTY).compose(s, res); else - throw new Exception("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for "+fn); } else if (version.startsWith("1.0")) { VersionConvertorAdvisor50 advisor = new org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor5(); org.hl7.fhir.dstu2.model.Resource res = VersionConvertor_10_50.convertResource(r, advisor); @@ -1758,7 +1957,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { else if (fn.endsWith(".json") && !fn.endsWith("template.json")) new org.hl7.fhir.dstu2.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu2.formats.IParser.OutputStyle.PRETTY).compose(s, res); else - throw new Exception("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for "+fn); } else if (version.equals(Constants.VERSION)) { if (fn.endsWith(".xml") && !fn.endsWith("template.xml")) new XmlParser().setOutputStyle(org.hl7.fhir.r5.formats.IParser.OutputStyle.PRETTY).compose(s, r); @@ -1767,9 +1966,9 @@ public class ValidationEngine implements IValidatorResourceFetcher { else if (fn.endsWith(".txt") || fn.endsWith(".map") ) TextFile.stringToStream(org.hl7.fhir.r5.utils.StructureMapUtilities.render((org.hl7.fhir.r5.model.StructureMap) r), s, false); else - throw new Exception("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for "+fn); } else - throw new Exception("Encounted unsupported configured version "+version+" loading "+fn); + throw new FHIRException("Encounted unsupported configured version "+version+" loading "+fn); s.close(); } @@ -1833,7 +2032,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { } else if (VersionUtilities.isR4Ver(version)) { return convertVersionNativeR4(targetVer, cnt, format); } else { - throw new Exception("Source version not supported yet: "+version); + throw new FHIRException("Source version not supported yet: "+version); } } catch (Exception e) { System.out.println("Conversion failed using Java convertor: "+e.getMessage()); @@ -1940,7 +2139,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); } } else { - throw new Exception("Target Version not supported yet: "+targetVer); + throw new FHIRException("Target Version not supported yet: "+targetVer); } } @@ -2009,7 +2208,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); } } else { - throw new Exception("Target Version not supported yet: "+targetVer); + throw new FHIRException("Target Version not supported yet: "+targetVer); } } @@ -2078,7 +2277,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); } } else { - throw new Exception("Target Version not supported yet: "+targetVer); + throw new FHIRException("Target Version not supported yet: "+targetVer); } } @@ -2147,7 +2346,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); } } else { - throw new Exception("Target Version not supported yet: "+targetVer); + throw new FHIRException("Target Version not supported yet: "+targetVer); } } @@ -2165,5 +2364,10 @@ public class ValidationEngine implements IValidatorResourceFetcher { } } + + public FilesystemPackageCacheManager getPcm() { + return pcm; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Validator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Validator.java index c903ac741..8ea219d5b 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Validator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Validator.java @@ -65,6 +65,7 @@ import org.hl7.fhir.r5.model.ImplementationGuide; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.validation.ValidationEngine.VersionSourceInformation; import org.hl7.fhir.validation.cli.ValidatorGui; import org.hl7.fhir.validation.cli.services.ComparisonService; import org.hl7.fhir.validation.cli.services.ValidationService; @@ -171,6 +172,10 @@ public class Validator { Display.printCliArgumentsAndInfo(args); cliContext = Params.loadCliContext(args); + if (cliContext.getSv() == null) { + cliContext.setSv(determineVersion(cliContext)); + } + // Comment this out because definitions filename doesn't necessarily contain version (and many not even be 14 characters long). Version gets spit out a couple of lines later after we've loaded the context String definitions = VersionUtilities.packageForVersion(cliContext.getSv()) + "#" + VersionUtilities.getCurrentVersion(cliContext.getSv()); ValidationEngine validator = ValidationService.getValidator(cliContext, definitions); @@ -204,5 +209,27 @@ public class Validator { } } } + } + + public static String determineVersion(CliContext cliContext) throws Exception { + if (cliContext.getMode() != EngineMode.VALIDATION) { + return "current"; + } + System.out.println("Scanning for versions (no -version parameter):"); + VersionSourceInformation versions = ValidationService.scanForVersions(cliContext); + for (String s : versions.getReport()) { + System.out.println(" "+s); + } + if (versions.isEmpty()) { + System.out.println("-> Using Default version '"+VersionUtilities.CURRENT_VERSION+"'"); + return "current"; + } + if (versions.size() == 1) { + System.out.println("-> use version "+versions.version()); + return versions.version(); + } + throw new Exception("-> Multiple versions found. Specify a particular version using the -version parameter"); + } + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java index 668629e25..1b0bda203 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java @@ -36,7 +36,7 @@ public class CliContext { @JsonProperty("txServer") private String txServer = "http://tx.fhir.org"; @JsonProperty("sv") - private String sv = "current"; + private String sv = null; @JsonProperty("txLog") private String txLog = null; @JsonProperty("mapLog") diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java new file mode 100644 index 000000000..90e24d387 --- /dev/null +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java @@ -0,0 +1,109 @@ +package org.hl7.fhir.validation.cli.services; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Locale; + +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.Element; +import org.hl7.fhir.r5.utils.IResourceValidator.IValidatorResourceFetcher; +import org.hl7.fhir.r5.utils.IResourceValidator.ReferenceValidationPolicy; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.utilities.VersionUtilities.VersionURLInfo; +import org.hl7.fhir.utilities.cache.BasePackageCacheManager; +import org.hl7.fhir.utilities.cache.FilesystemPackageCacheManager; +import org.hl7.fhir.utilities.cache.NpmPackage; +import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher.IPackageInstaller; + +public class StandAloneValidatorFetcher implements IValidatorResourceFetcher { + + public interface IPackageInstaller { + public void loadIg(String src, boolean recursive) throws IOException, FHIRException; + } + + private BasePackageCacheManager pcm; + private IWorkerContext context; + private IPackageInstaller installer; + + public StandAloneValidatorFetcher(FilesystemPackageCacheManager pcm, IWorkerContext context, IPackageInstaller installer) { + super(); + this.pcm = pcm; + this.context = context; + this.installer = installer; + } + + @Override + public Element fetch(Object appContext, String url) throws FHIRFormatError, DefinitionException, FHIRException, IOException { + throw new Error("Not done yet"); + } + + @Override + public ReferenceValidationPolicy validationPolicy(Object appContext, String path, String url) { + throw new Error("Not done yet"); + } + + @Override + public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException { + if (!Utilities.isAbsoluteUrl(url)) { + return false; + } + // if we've got to here, it's a reference to a FHIR URL. We're going to try to resolve it on the fly + + // first possibility: it's a reference to a version specific URL http://hl7.org/fhir/X.X/... + VersionURLInfo vu = VersionUtilities.parseVersionUrl(url); + if (vu != null) { + NpmPackage pi = pcm.loadPackage(VersionUtilities.packageForVersion(vu.getVersion()), VersionUtilities.getCurrentVersion(vu.getVersion())); + return pi.hasCanonical(vu.getUrl()); + } + + // ok maybe it's a reference to a package we know + String base = findBaseUrl(url); + String pid = pcm.getPackageId(base); + if (url.contains("|")) { + pid = pid+"#"+url.substring(url.indexOf("|")+1); + } + if (pid != null) { + installer.loadIg(pid, false); + NpmPackage pi = pcm.loadPackage(pid); + return pi.hasCanonical(url); + } + + if (!url.startsWith("http://hl7.org/fhir")) { + return true; // we don't bother with those in the standalone validator - we assume they are valid + } + + // we assume it's invalid at this point + return false; + + } + + private String findBaseUrl(String url) { + String[] p = url.split("\\/"); + for (int i = 1; i< p.length; i++) { + if (Utilities.existsInList(p[i], context.getResourceNames())) { + StringBuilder b = new StringBuilder(p[0]); + for (int j = 1; j < i; j++) { + b.append("/"); + b.append(p[j]); + } + return b.toString(); + } + } + return null; + } + + @Override + public byte[] fetchRaw(String url) throws MalformedURLException, IOException { + throw new Error("Not done yet"); + } + + @Override + public void setLocale(Locale locale) { + throw new Error("Not done yet"); + } + +} diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java index 08c9e4741..9c331c052 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java @@ -11,6 +11,7 @@ import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.validation.ValidationEngine; +import org.hl7.fhir.validation.ValidationEngine.VersionSourceInformation; import org.hl7.fhir.validation.cli.model.*; import java.io.File; @@ -52,6 +53,15 @@ public class ValidationService { return response; } + public static VersionSourceInformation scanForVersions(CliContext cliContext) throws Exception { + VersionSourceInformation versions = new VersionSourceInformation(); + ValidationEngine ve = new ValidationEngine(); + for (String src : cliContext.getIgs()) { + ve.scanForIgVersion(src, cliContext.isRecursive(), versions); + } + ve.scanForVersions(cliContext.getSources(), versions); + return versions; + } public static void validateSources(CliContext cliContext, ValidationEngine validator, long loadStart) throws Exception { validator.doneLoading(loadStart); if (cliContext.getProfiles().size() > 0) { @@ -205,6 +215,7 @@ public class ValidationService { validator.setSecurityChecks(cliContext.isSecurityChecks()); validator.setCrumbTrails(cliContext.isCrumbTrails()); validator.setShowTimes(cliContext.isShowTimes()); + validator.setFetcher(new StandAloneValidatorFetcher(validator.getPcm(), validator.getContext(), validator)); TerminologyCache.setNoCaching(cliContext.isNoInternalCaching()); return 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 3b72413a2..b2d890072 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 @@ -151,6 +151,7 @@ import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities.DecimalStatus; import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.utilities.VersionUtilities.VersionURLInfo; import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; @@ -592,8 +593,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat throw new FHIRException(e1); } timeTracker.load(t); - if (e != null) + if (e != null) { validate(appContext, errors, e, profiles); + } return e; } @@ -1856,7 +1858,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (fetcher != null) { boolean found; try { - found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url); + found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url); } catch (IOException e1) { found = false; } @@ -2928,7 +2930,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return rr; } } - if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY) { + if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY || stack.getElement().getSpecial() == SpecialElement.PARAMETER) { return null; // we don't try to resolve contained references across this boundary } stack = stack.getParent(); @@ -2963,6 +2965,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return rr; } } + if (stack.getElement().getSpecial() == SpecialElement.PARAMETER && stack.getParent() != null) { + NodeStack tgt = findInParams(stack.getParent().getParent(), ref); + if (tgt != null) { + ResolvedReference rr = new ResolvedReference(); + rr.setResource(tgt.getElement()); + rr.setFocus(tgt.getElement()); + rr.setExternal(false); + rr.setStack(tgt); + rr.getStack().qualifyPath(".ofType("+tgt.getElement().fhirType()+")"); + return rr; + } + } stack = stack.getParent(); } // we can get here if we got called via FHIRPath conformsTo which breaks the stack continuity. @@ -2989,6 +3003,42 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return null; } + private NodeStack findInParams(NodeStack params, String ref) { + int i = 0; + for (Element child : params.getElement().getChildren("parameter")) { + NodeStack p = params.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition()); + if (child.hasChild("resource")) { + Element res = child.getNamedChild("resource"); + if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) { + return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition()); + } + } + NodeStack pc = findInParamParts(p, child, ref); + if (pc != null) { + return pc; + } + } + return null; + } + + private NodeStack findInParamParts(NodeStack pp, Element param, String ref) { + int i = 0; + for (Element child : param.getChildren("part")) { + NodeStack p = pp.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition()); + if (child.hasChild("resource")) { + Element res = child.getNamedChild("resource"); + if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) { + return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition()); + } + } + NodeStack pc = findInParamParts(p, child, ref); + if (pc != null) { + return pc; + } + } + return null; + } + private Element getEntryForSource(Element bundle, Element element) { List entries = new ArrayList(); bundle.getNamedChildren(ENTRY, entries); @@ -3432,7 +3482,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat for (Element profile : profiles) { StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile.primitiveValue()); if (!defn.getUrl().equals(profile.primitiveValue())) { - if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", sd != null, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue())) { + // is this a version specific reference? + VersionURLInfo vu = VersionUtilities.parseVersionUrl(profile.primitiveValue()); + if (vu != null) { + if (!VersionUtilities.versionsCompatible(vu.getVersion(), context.getVersion())) { + hint(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_OTHER_VERSION, vu.getVersion()); + } else if (vu.getUrl().equals(defn.getUrl())) { + hint(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_THIS_VERSION_OK); + } else { + StructureDefinition sdt = context.fetchResource(StructureDefinition.class, vu.getUrl()); + rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER, sdt == null ? "null" : sdt.getType()); + } + } else if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", sd != null, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue())) { signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), !crumbTrails, I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_META, sd.getUrl()); stack.resetIds(); startInner(hostContext, errors, resource, element, sd, stack, false); @@ -3658,9 +3719,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (trr == null) { rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE, resourceName); } else if (isValidResourceType(resourceName, trr)) { - long t = System.nanoTime(); - StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName); - timeTracker.sd(t); // special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise ValidatorHostContext hc = null; if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY || element.getSpecial() == SpecialElement.BUNDLE_OUTCOME || element.getSpecial() == SpecialElement.PARAMETER) { @@ -3669,10 +3727,29 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else { hc = hostContext.forContained(element); } - trackUsage(profile, hostContext, element); stack.resetIds(); - if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE, resourceName)) { - validateResource(hc, errors, resource, element, profile, idstatus, stack); + if (trr.getProfile().size() == 1) { + long t = System.nanoTime(); + StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, trr.getProfile().get(0).asStringValue()); + timeTracker.sd(t); + trackUsage(profile, hostContext, element); + if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE, resourceName)) { + validateResource(hc, errors, resource, element, profile, idstatus, stack); + } + } else if (trr.getProfile().size() == 0) { + long t = System.nanoTime(); + StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName); + timeTracker.sd(t); + trackUsage(profile, hostContext, element); + if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE, resourceName)) { + validateResource(hc, errors, resource, element, profile, idstatus, stack); + } + } else { + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); + for (CanonicalType u : trr.getProfile()) { + b.append(u.asStringValue()); + } + rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES, trr.getCode(), b.toString()); } } else { List types = new ArrayList<>(); From 6bdde22026de24e169f5954d19685b48d22a568a Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 3 Aug 2020 18:16:00 +1000 Subject: [PATCH 8/9] no validation for CanonicalResource.url (further work needed) --- RELEASE_NOTES.md | 6 +- .../hl7/fhir/utilities/VersionUtilities.java | 129 ++++++++++++++++-- .../instance/InstanceValidator.java | 38 ++++-- .../validation/tests/ValidationTestSuite.java | 2 +- 4 files changed, 154 insertions(+), 21 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 1fea49ef9..84027fd66 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,8 +1,10 @@ Validator Fixes: +* better validation of external references. Note: this is a potentially significant change: things that were called ok before may not be now, and things that were not ok before may become so, depending on the interplay between this and auto-load, further work may be needed here +* Support better validation of version specific profiles in meta.profile. This may also find new errors that were not previously being found * Support auto-determination of the version of FHIR to use when using the java validator * auto-load packages from the package server when references to profiles etc are encountered -* Support better validation of version specific profiles in meta.profile * look for references inside other parameters in Parameters resource +* no validation for CanonicalResource.url (further work needed) Other Code changes: * Rendering: add rendering for Parameters resources @@ -14,5 +16,5 @@ Other Code changes: TODO before commit: * check version of contained resources -* review validation of CanonicalResource.url + diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java index 544f83079..c17a684e5 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java @@ -1,24 +1,27 @@ package org.hl7.fhir.utilities; +import java.util.ArrayList; +import java.util.List; + import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.utilities.cache.NpmPackage; /* Copyright (c) 2011+, HL7, Inc. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to + * Neither the name of HL7 nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -29,13 +32,13 @@ import org.hl7.fhir.utilities.cache.NpmPackage; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + */ public class VersionUtilities { - - + + public static class VersionURLInfo { private String version; private String url; @@ -163,7 +166,7 @@ public class VersionUtilities { public static String getMajMin(String version) { if (version == null) return null; - + if (Utilities.charCount(version, '.') == 1) { String[] p = version.split("\\."); return p[0]+"."+p[1]; @@ -256,4 +259,110 @@ public class VersionUtilities { return null; } + public static List getCanonicalResourceNames(String version) { + ArrayList res = new ArrayList(); + if (isR2Ver(version) || isR2BVer(version)) { + res.add("ValueSet"); + res.add("ConceptMap"); + res.add("NamingSystem"); + res.add("StructureDefinition"); + res.add("DataElement"); + res.add("Conformance"); + res.add("OperationDefinition"); + res.add("SearchParameter"); + res.add("ImplementationGuide"); + res.add("TestScript"); + } + if (isR3Ver(version)) { + res.add("CapabilityStatement"); + res.add("StructureDefinition"); + res.add("ImplementationGuide"); + res.add("SearchParameter"); + res.add("MessageDefinition"); + res.add("OperationDefinition"); + res.add("CompartmentDefinition"); + res.add("StructureMap"); + res.add("GraphDefinition"); + res.add("DataElement"); + res.add("CodeSystem"); + res.add("ValueSet"); + res.add("ConceptMap"); + res.add("ExpansionProfile"); + res.add("Questionnaire"); + res.add("ActivityDefinition"); + res.add("ServiceDefinition"); + res.add("PlanDefinition"); + res.add("Measure"); + res.add("TestScript"); + + } + if (isR4Ver(version)) { + + res.add("ActivityDefinition"); + res.add("CapabilityStatement"); + res.add("ChargeItemDefinition"); + res.add("CodeSystem"); + res.add("CompartmentDefinition"); + res.add("ConceptMap"); + res.add("EffectEvidenceSynthesis"); + res.add("EventDefinition"); + res.add("Evidence"); + res.add("EvidenceVariable"); + res.add("ExampleScenario"); + res.add("GraphDefinition"); + res.add("ImplementationGuide"); + res.add("Library"); + res.add("Measure"); + res.add("MessageDefinition"); + res.add("NamingSystem"); + res.add("OperationDefinition"); + res.add("PlanDefinition"); + res.add("Questionnaire"); + res.add("ResearchDefinition"); + res.add("ResearchElementDefinition"); + res.add("RiskEvidenceSynthesis"); + res.add("SearchParameter"); + res.add("StructureDefinition"); + res.add("StructureMap"); + res.add("TerminologyCapabilities"); + res.add("TestScript"); + res.add("ValueSet"); + } + + if (isR5Ver(version)) { + + res.add("ActivityDefinition"); + res.add("CapabilityStatement"); + res.add("CapabilityStatement2"); + res.add("ChargeItemDefinition"); + res.add("Citation"); + res.add("CodeSystem"); + res.add("CompartmentDefinition"); + res.add("ConceptMap"); + res.add("ConditionDefinition"); + res.add("EventDefinition"); + res.add("Evidence"); + res.add("EvidenceReport"); + res.add("EvidenceVariable"); + res.add("ExampleScenario"); + res.add("GraphDefinition"); + res.add("ImplementationGuide"); + res.add("Library"); + res.add("Measure"); + res.add("MessageDefinition"); + res.add("NamingSystem"); + res.add("OperationDefinition"); + res.add("PlanDefinition"); + res.add("Questionnaire"); + res.add("SearchParameter"); + res.add("StructureDefinition"); + res.add("StructureMap"); + res.add("TerminologyCapabilities"); + res.add("TestScript"); + res.add("ValueSet"); + + } + return res; + } + } \ No newline at end of file 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 b2d890072..554bded5a 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 @@ -1854,15 +1854,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("#") || Utilities.isAbsoluteUrl(url), I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url); } - // now, do we check the URI target? - if (fetcher != null) { - boolean found; - try { - found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url); - } catch (IOException e1) { - found = false; + if (isCanonicalURLElement(e)) { + // for now, no validation. Need to think about authority. + } else { + // now, do we check the URI target? + if (fetcher != null) { + boolean found; + try { + found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url); + } catch (IOException e1) { + found = false; + } + rule(errors, IssueType.INVALID, e.line(), e.col(), path, found, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url); } - rule(errors, IssueType.INVALID, e.line(), e.col(), path, found, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url); } } if (type.equals(ID)) { @@ -2009,6 +2013,24 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // for nothing to check } + private boolean isCanonicalURLElement(Element e) { + if (e.getProperty() == null || e.getProperty().getDefinition() == null) { + return false; + } + String path = e.getProperty().getDefinition().getBase().getPath(); + if (path == null) { + return false; + } + String[] p = path.split("\\."); + if (p.length != 2) { + return false; + } + if (!"url".equals(p[1])) { + return false; + } + return Utilities.existsInList(p[0], VersionUtilities.getCanonicalResourceNames(context.getVersion())); + } + private boolean containsHtmlTags(String cnt) { int i = cnt.indexOf("<"); while (i > -1) { diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java index e1bd5dece..f9af15a9c 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java @@ -457,7 +457,7 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour @Override public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException { - return !url.contains("example.org"); + return !url.contains("example.org") && !url.startsWith("http://hl7.org/fhir/invalid"); } @Override From 417c245e3c3fe10e9f80ae40168827bfff6dbb72 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 3 Aug 2020 21:52:39 +1000 Subject: [PATCH 9/9] fix up release notes --- RELEASE_NOTES.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 84027fd66..4ccbaf10b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -14,7 +14,3 @@ Other Code changes: * Fix NPE bug in value set comparison -TODO before commit: -* check version of contained resources - -