diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6bd9878c4..1126aab1f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,5 @@ Validator: -* no changes +* fix processing of modifier extensions and cross-version modifier extensions Other changes: * improvements to data types rendering based on new test cases (URLs, Money, Markdown) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/XVerPackegeFixer.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/XVerPackegeFixer.java new file mode 100644 index 000000000..634e3107b --- /dev/null +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/XVerPackegeFixer.java @@ -0,0 +1,387 @@ +package org.hl7.fhir.convertors.misc; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.naming.ldap.StartTlsRequest; +import javax.xml.parsers.ParserConfigurationException; + +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.json.JsonTrackingParser; +import org.xml.sax.SAXException; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public class XVerPackegeFixer { + + private static final String R5_FOLDER = "C:\\work\\org.hl7.fhir\\packages\\hl7.fhir.rX\\hl7.fhir.r5.core\\package"; + private static final String R4_FOLDER = "C:\\work\\org.hl7.fhir\\packages\\hl7.fhir.rX\\hl7.fhir.r4.core\\package"; + private static final String R3_FOLDER = "C:\\work\\org.hl7.fhir\\packages\\hl7.fhir.rX\\hl7.fhir.r3.core\\package"; + private static final String R2B_FOLDER = "C:\\work\\org.hl7.fhir\\packages\\hl7.fhir.rX\\hl7.fhir.r2b.core\\package"; + private static final String R2_FOLDER = "C:\\work\\org.hl7.fhir\\packages\\hl7.fhir.rX\\hl7.fhir.r2.core\\package"; + private static int mod; + + private static Map map5 = new HashMap<>(); + private static Map map4 = new HashMap<>(); + private static Map map3 = new HashMap<>(); + private static Map map2 = new HashMap<>(); + private static Map map2b = new HashMap<>(); + + public static void main(String[] args) throws FileNotFoundException, ParserConfigurationException, SAXException, IOException { + mod = 0; + for (File f : new File(args[0]).listFiles()) { + if (f.getName().startsWith("xver-")) { + JsonObject j = JsonTrackingParser.parseJson(f); + fixUp(j, f.getName()); + JsonTrackingParser.write(j, f, true); + } + } + System.out.println("all done: "+mod+" modifiers"); + } + + private static void fixUp(JsonObject j, String name) throws FHIRFormatError, FileNotFoundException, IOException { + name = name.replace(".json", ""); + System.out.println("Process "+name); + String version = name.substring(name.lastIndexOf("-")+1); + int i = 0; + for (Entry e : j.entrySet()) { + if (i == 50) { + i = 0; + System.out.print("."); + } + i++; + String n = e.getKey(); + JsonObject o = ((JsonObject) e.getValue()); + boolean ok = (o.has("types") && o.getAsJsonArray("types").size() > 0) || (o.has("elements") && o.getAsJsonArray("elements").size() > 0); + if (!ok) { + List types = new ArrayList<>(); + List elements = new ArrayList<>(); + getElementInfo(version, n, types, elements); + if (elements.size() > 0) { + JsonArray arr = o.getAsJsonArray("elements"); + if (arr == null) { + arr = new JsonArray(); + o.add("elements", arr); + } + for (String s : types) { + arr.add(s); + } + } else if (types.size() > 0) { + JsonArray arr = o.getAsJsonArray("types"); + if (arr == null) { + arr = new JsonArray(); + o.add("types", arr); + } + for (String s : types) { + arr.add(s); + } + } + } + } + System.out.println("done"); + } + + private static boolean getElementInfo(String version, String n, List types, List elements) throws FHIRFormatError, FileNotFoundException, IOException { + if ("contained".equals(n.substring(n.indexOf(".")+1))) { + return false; + } + switch (version) { + case "4.6": return getElementInfoR5(n, types, elements); + case "4.0": return getElementInfoR4(n, types, elements); + case "3.0": return getElementInfoR3(n, types, elements); + case "1.4": return getElementInfoR2B(n, types, elements); + case "1.0": return getElementInfoR2(n, types, elements); + } + return false; + } + + private static Object tail(String value) { + return value.contains("/") ? value.substring(value.lastIndexOf("/")+1) : value; + } + + private static boolean getElementInfoR5(String n, List types, List elements) throws FHIRFormatError, FileNotFoundException, IOException { + String tn = n.substring(0, n.indexOf(".")); + org.hl7.fhir.r5.model.StructureDefinition sd = null; + if (map5.containsKey(tn)) { + sd = map5.get(tn); + } else { + sd = (org.hl7.fhir.r5.model.StructureDefinition) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(R5_FOLDER, "StructureDefinition-"+tn+".json"))); + map5.put(tn, sd); + } + for (org.hl7.fhir.r5.model.ElementDefinition ed : sd.getSnapshot().getElement()) { + if (ed.getPath().equals(n)) { + List children = listChildrenR5(sd.getSnapshot().getElement(), ed); + if (children.size() > 0) { + for (org.hl7.fhir.r5.model.ElementDefinition c : children) { + String en = c.getPath().substring(ed.getPath().length()+1); + elements.add(en); + } + } else { + for (org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent t : ed.getType()) { + if (t.hasTargetProfile()) { + StringBuilder b = new StringBuilder(); + b.append(t.getWorkingCode()); + b.append("("); + boolean first = true; + for (org.hl7.fhir.r5.model.CanonicalType u : t.getTargetProfile()) { + if (first) first = false; else b.append("|"); + b.append(tail(u.getValue())); + } + b.append(")"); + types.add(b.toString()); + } else { + types.add(t.getWorkingCode()); + } + } + } + } + } + return false; + } + + + private static List listChildrenR5(List list, org.hl7.fhir.r5.model.ElementDefinition ed) { + List res = new ArrayList<>(); + for (org.hl7.fhir.r5.model.ElementDefinition t : list) { + String p = t.getPath(); + if (p.startsWith(ed.getPath()+".")) { + p = p.substring(ed.getPath().length()+1); + if (!p.contains(".")) { + res.add(t); + } + } + } + return res; + } + + private static boolean getElementInfoR4(String n, List types, List elements) throws FHIRFormatError, FileNotFoundException, IOException { + String tn = n.substring(0, n.indexOf(".")); + org.hl7.fhir.r4.model.StructureDefinition sd = null; + if (map4.containsKey(tn)) { + sd = map4.get(tn); + } else { + sd = (org.hl7.fhir.r4.model.StructureDefinition) new org.hl7.fhir.r4.formats.JsonParser().parse(new FileInputStream(Utilities.path(R4_FOLDER, "StructureDefinition-"+tn+".json"))); + map4.put(tn, sd); + } + for (org.hl7.fhir.r4.model.ElementDefinition ed : sd.getSnapshot().getElement()) { + if (ed.getPath().equals(n)) { + List children = listChildrenR4(sd.getSnapshot().getElement(), ed); + if (children.size() > 0) { + for (org.hl7.fhir.r4.model.ElementDefinition c : children) { + String en = c.getPath().substring(ed.getPath().length()+1); + elements.add(en); + } + } else { + for (org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent t : ed.getType()) { + if (t.hasTargetProfile()) { + StringBuilder b = new StringBuilder(); + b.append(t.getWorkingCode()); + b.append("("); + boolean first = true; + for (org.hl7.fhir.r4.model.CanonicalType u : t.getTargetProfile()) { + if (first) first = false; else b.append("|"); + b.append(tail(u.getValue())); + } + b.append(")"); + types.add(b.toString()); + } else { + types.add(t.getWorkingCode()); + } + } + } + } + } + return false; + } + + + private static List listChildrenR4(List list, org.hl7.fhir.r4.model.ElementDefinition ed) { + List res = new ArrayList<>(); + for (org.hl7.fhir.r4.model.ElementDefinition t : list) { + String p = t.getPath(); + if (p.startsWith(ed.getPath()+".")) { + p = p.substring(ed.getPath().length()+1); + if (!p.contains(".")) { + res.add(t); + } + } + } + return res; + } + + + private static boolean getElementInfoR3(String n, List types, List elements) throws FHIRFormatError, FileNotFoundException, IOException { + String tn = n.substring(0, n.indexOf(".")); + org.hl7.fhir.dstu3.model.StructureDefinition sd = null; + if (map3.containsKey(tn)) { + sd = map3.get(tn); + } else { + sd = (org.hl7.fhir.dstu3.model.StructureDefinition) new org.hl7.fhir.dstu3.formats.JsonParser().parse(new FileInputStream(Utilities.path(R3_FOLDER, "StructureDefinition-"+tn+".json"))); + map3.put(tn, sd); + } + for (org.hl7.fhir.dstu3.model.ElementDefinition ed : sd.getSnapshot().getElement()) { + if (ed.getPath().equals(n)) { + List children = listChildrenR3(sd.getSnapshot().getElement(), ed); + if (children.size() > 0) { + for (org.hl7.fhir.dstu3.model.ElementDefinition c : children) { + String en = c.getPath().substring(ed.getPath().length()+1); + elements.add(en); + } + } else { + for (org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent t : ed.getType()) { + if (t.hasTargetProfile()) { + StringBuilder b = new StringBuilder(); + b.append(t.getCode()); + b.append("("); + b.append(tail(t.getTargetProfile())); + b.append(")"); + types.add(b.toString()); + } else { + types.add(t.getCode()); + } + } + } + } + } + return false; + } + + + private static List listChildrenR3(List list, org.hl7.fhir.dstu3.model.ElementDefinition ed) { + List res = new ArrayList<>(); + for (org.hl7.fhir.dstu3.model.ElementDefinition t : list) { + String p = t.getPath(); + if (p.startsWith(ed.getPath()+".")) { + p = p.substring(ed.getPath().length()+1); + if (!p.contains(".")) { + res.add(t); + } + } + } + return res; + } + + + private static boolean getElementInfoR2(String n, List types, List elements) throws FHIRFormatError, FileNotFoundException, IOException { + String tn = n.substring(0, n.indexOf(".")); + org.hl7.fhir.dstu2.model.StructureDefinition sd = null; + if (map2.containsKey(tn)) { + sd = map2.get(tn); + } else { + sd = (org.hl7.fhir.dstu2.model.StructureDefinition) new org.hl7.fhir.dstu2.formats.JsonParser().parse(new FileInputStream(Utilities.path(R2_FOLDER, "StructureDefinition-"+tn+".json"))); + map2.put(tn, sd); + } + for (org.hl7.fhir.dstu2.model.ElementDefinition ed : sd.getSnapshot().getElement()) { + if (ed.getPath().equals(n)) { + List children = listChildrenR2(sd.getSnapshot().getElement(), ed); + if (children.size() > 0) { + for (org.hl7.fhir.dstu2.model.ElementDefinition c : children) { + String en = c.getPath().substring(ed.getPath().length()+1); + elements.add(en); + } + } else { + for (org.hl7.fhir.dstu2.model.ElementDefinition.TypeRefComponent t : ed.getType()) { + if (t.hasProfile()) { + StringBuilder b = new StringBuilder(); + b.append(t.getCode()); + b.append("("); + boolean first = true; + for (org.hl7.fhir.dstu2.model.UriType u : t.getProfile()) { + if (first) first = false; else b.append("|"); + b.append(tail(u.getValue())); + } + b.append(")"); + types.add(b.toString()); + } else { + types.add(t.getCode()); + } + } + } + } + } + return false; + } + + + private static List listChildrenR2(List list, org.hl7.fhir.dstu2.model.ElementDefinition ed) { + List res = new ArrayList<>(); + for (org.hl7.fhir.dstu2.model.ElementDefinition t : list) { + String p = t.getPath(); + if (p.startsWith(ed.getPath()+".")) { + p = p.substring(ed.getPath().length()+1); + if (!p.contains(".")) { + res.add(t); + } + } + } + return res; + } + + + private static boolean getElementInfoR2B(String n, List types, List elements) throws FHIRFormatError, FileNotFoundException, IOException { + String tn = n.substring(0, n.indexOf(".")); + org.hl7.fhir.dstu2016may.model.StructureDefinition sd = null; + if (map2b.containsKey(tn)) { + sd = map2b.get(tn); + } else { + sd = (org.hl7.fhir.dstu2016may.model.StructureDefinition) new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(new FileInputStream(Utilities.path(R2B_FOLDER, "StructureDefinition-"+tn+".json"))); + map2b.put(tn, sd); + } + for (org.hl7.fhir.dstu2016may.model.ElementDefinition ed : sd.getSnapshot().getElement()) { + if (ed.getPath().equals(n)) { + List children = listChildrenR2B(sd.getSnapshot().getElement(), ed); + if (children.size() > 0) { + for (org.hl7.fhir.dstu2016may.model.ElementDefinition c : children) { + String en = c.getPath().substring(ed.getPath().length()+1); + elements.add(en); + } + } else { + for (org.hl7.fhir.dstu2016may.model.ElementDefinition.TypeRefComponent t : ed.getType()) { + if (t.hasProfile()) { + StringBuilder b = new StringBuilder(); + b.append(t.getCode()); + b.append("("); + boolean first = true; + for (org.hl7.fhir.dstu2016may.model.UriType u : t.getProfile()) { + if (first) first = false; else b.append("|"); + b.append(tail(u.getValue())); + } + b.append(")"); + types.add(b.toString()); + } else { + types.add(t.getCode()); + } + } + } + } + } + return false; + } + + + private static List listChildrenR2B(List list, org.hl7.fhir.dstu2016may.model.ElementDefinition ed) { + List res = new ArrayList<>(); + for (org.hl7.fhir.dstu2016may.model.ElementDefinition t : list) { + String p = t.getPath(); + if (p.startsWith(ed.getPath()+".")) { + p = p.substring(ed.getPath().length()+1); + if (!p.contains(".")) { + res.add(t); + } + } + } + return res; + } + + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/XVerExtensionManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/XVerExtensionManager.java index 6da8f1cbe..ddf283329 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/XVerExtensionManager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/XVerExtensionManager.java @@ -133,6 +133,11 @@ public class XVerExtensionManager { } else { throw new FHIRException("Internal error - attempt to define extension for "+url+" when it is invalid"); } + if (path.has("modifier") && path.get("modifier").getAsBoolean()) { + ElementDefinition baseDef = new ElementDefinition("Extension"); + sd.getDifferential().getElement().add(0, baseDef); + baseDef.setIsModifier(true); + } return sd; } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java index 6fba41e07..b37965b2c 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java @@ -664,6 +664,12 @@ public class JsonTrackingParser { TextFile.stringToFile(jcnt, file); } + public static void write(JsonObject json, File file, boolean pretty) throws IOException { + Gson gson = pretty ? new GsonBuilder().setPrettyPrinting().create() : new GsonBuilder().create(); + String jcnt = gson.toJson(json); + TextFile.stringToFile(jcnt, file); + } + public static void write(JsonObject json, String fileName) throws IOException { Gson gson = new GsonBuilder().setPrettyPrinting().create(); String jcnt = gson.toJson(json); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/CommonPackages.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/CommonPackages.java index b65e206e7..284cabed8 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/CommonPackages.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/CommonPackages.java @@ -3,7 +3,7 @@ package org.hl7.fhir.utilities.npm; public class CommonPackages { public static final String ID_XVER = "hl7.fhir.xver-extensions"; - public static final String VER_XVER = "0.0.7"; + public static final String VER_XVER = "0.0.8"; public static final String ID_PUBPACK = "hl7.fhir.pubpack"; public static final String VER_PUBPACK = "0.0.9"; 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 3e357a716..0f08b395a 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 @@ -1476,7 +1476,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private String describeValueSet(String url) { ValueSet vs = context.fetchResource(ValueSet.class, url); if (vs != null) { - return "\""+vs.present()+"\" ("+url+")"; + return "'"+vs.present()+"' ("+url+")"; } else { return "("+url+")"; } @@ -1656,7 +1656,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private StructureDefinition checkExtension(ValidatorHostContext hostContext, List errors, String path, Element resource, Element container, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack, NodeStack containerStack, String extensionUrl) throws FHIRException { String url = element.getNamedChildValue("url"); boolean isModifier = element.getName().equals("modifierExtension"); - + assert def.getIsModifier() == isModifier; + long t = System.nanoTime(); StructureDefinition ex = Utilities.isAbsoluteUrl(url) ? context.fetchResource(StructureDefinition.class, url) : null; timeTracker.sd(t); @@ -1676,10 +1677,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } if (ex != null) { trackUsage(ex, hostContext, element); - if (def.getIsModifier()) { - rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHY); + // check internal definitions are coherent + if (isModifier) { + rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHY); } else { - rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHN); + rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHN); } // two questions // 1. can this extension be used here? diff --git a/pom.xml b/pom.xml index b04f222b4..da1ae4e0f 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 5.1.0 - 1.1.74 + 1.1.75 5.7.1 1.7.1 3.0.0-M5