diff --git a/.github/workflows/license-check/license-check.sh b/.github/workflows/license-check/license-check.sh new file mode 100755 index 000000000..b23dc8440 --- /dev/null +++ b/.github/workflows/license-check/license-check.sh @@ -0,0 +1,49 @@ +#!/bin/bash +IFS=$'\n' + +readarray -t whitelist < ./.github/workflows/license-check/license-whitelist.txt +readarray -t specialcases < <( grep -vE "^#" ./.github/workflows/license-check/license-special-cases.txt ) + +exitStatus=0 + +for specialcase in "${specialcases[@]}" +do + echo "Special case: " "$specialcase" +done + +readarray -t thirdparty < <( tail -n +3 ./target/generated-sources/license/THIRD-PARTY.txt ) +for thirdpartyentry in "${thirdparty[@]}" +do + allLicensesValid=true + # Remove leading spaces + thirdpartyentry="${thirdpartyentry#"${thirdpartyentry%%[![:space:]]*}"}" + echo "Evaluating Dependency: " "$thirdpartyentry" + if [[ $(echo "${specialcases[@]}" | fgrep -w $thirdpartyentry) ]] + then + echo " Ignoring: " "$thirdpartyentry" + else + licenses=($(echo $thirdpartyentry | awk -vRS=")" -vFS="(" '{print $2}')) + for (( i=0; i < ${#licenses[@]} - 1 ; i++ )) + do + #echo ${licenses[i]} + licenseToCheck=${licenses[i]} + if [[ $(echo "${whitelist[@]}" | fgrep -w $licenseToCheck) ]] + then + #do nothing bsh no-op + : + else + echo " Unknown license found: " $licenseToCheck + allLicensesValid=false + exitStatus=1 + fi + done + fi + if $allLicensesValid + then + echo " All licenses OK" + else + echo " Possible license incompatibilities found" + fi +done + +exit $exitStatus \ No newline at end of file diff --git a/.github/workflows/license-check/license-special-cases.txt b/.github/workflows/license-check/license-special-cases.txt new file mode 100644 index 000000000..906c41759 --- /dev/null +++ b/.github/workflows/license-check/license-special-cases.txt @@ -0,0 +1,3 @@ +(Unknown license) javaparser (com.google.code.javaparser:javaparser:1.0.11 - http://code.google.com/p/javaparser/) +# IGNORE ME +(Apache Software License 2.0) HAPI FHIR - Validation Resources (FHIR R4) (ca.uhn.hapi.fhir:hapi-fhir-validation-resources-r4:6.4.1 - https://hapifhir.io/hapi-deployable-pom/hapi-fhir-validation-resources-r4) \ No newline at end of file diff --git a/.github/workflows/license-check/license-whitelist.txt b/.github/workflows/license-check/license-whitelist.txt new file mode 100644 index 000000000..f734f12c1 --- /dev/null +++ b/.github/workflows/license-check/license-whitelist.txt @@ -0,0 +1,25 @@ +The Apache Software License, Version 2.0 +Apache Software License, version 1.1 +Apache Software License 2.0 +The Apache License, Version 2.0 +The Apache Software License, Version 2.0 +MIT License +The MIT License +Apache Software License, version 1.1 +Apache 2 +Apache 2.0 +Apache License 2.0 +Eclipse Public License v2.0 +BSD licence +The BSD License +BSD-Style License +BSD License 3 +New BSD License +BSD 3 Clause +The JSON License +Eclipse Public License - v 1.0 +Eclipse Public License v. 2.0 +Eclipse Distribution License v. 1.0 +Eclipse Distribution License - v 1.0 +Unicode/ICU License +BSD 2-Clause License \ No newline at end of file diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml new file mode 100644 index 000000000..86e85e017 --- /dev/null +++ b/.github/workflows/manual.yml @@ -0,0 +1,30 @@ +# This is a basic workflow that is manually triggered + +name: License Check + +on: + workflow_dispatch: + + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "greet" + check: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Collect module licenses + run: mvn clean install -DskipTests + - name: Collect module licenses + run: mvn license:add-third-party + - name: Aggregate licenses + run: mvn license:aggregate-add-third-party + - name: Set script permissions + run: chmod u+x .github/workflows/license-check/license-check.sh + - name: Run script + run: .github/workflows/license-check/license-check.sh + continue-on-error: true diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ec2c36118..7b06c6ab5 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,9 +1,7 @@ ## Validator Changes -* Fix invalid integer detection -* Improved invariant checking +* no changes ## Other code changes -* Update obligation handling code for split definitions -* Update ICF importer to handle grouping levels +* no changes \ No newline at end of file diff --git a/org.hl7.fhir.convertors/pom.xml b/org.hl7.fhir.convertors/pom.xml index 183f9efcb..934701ca5 100644 --- a/org.hl7.fhir.convertors/pom.xml +++ b/org.hl7.fhir.convertors/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.0.24-SNAPSHOT + 6.0.26-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CPTImporter.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CPTImporter.java new file mode 100644 index 000000000..36e70b339 --- /dev/null +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CPTImporter.java @@ -0,0 +1,309 @@ +package org.hl7.fhir.convertors.misc; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Date; +import java.util.Scanner; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.formats.IParser.OutputStyle; +import org.hl7.fhir.r5.formats.JsonParser; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.StringType; +import org.hl7.fhir.r5.model.CodeType; +import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent; +import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; +import org.hl7.fhir.r5.model.CodeSystem.PropertyType; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode; +import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; +import org.hl7.fhir.utilities.CSVReader; +import org.hl7.fhir.utilities.Utilities; + +public class CPTImporter { + + public static void main(String[] args) throws FHIRException, FileNotFoundException, IOException, ClassNotFoundException, SQLException { + new CPTImporter().doImport(args[0], args[1], args[2]); + + } + + private Connection con; + + private void doImport(String src, String version, String dst) throws FHIRException, FileNotFoundException, IOException, ClassNotFoundException, SQLException { + + CodeSystem cs = new CodeSystem(); + cs.setId("cpt"); + cs.setUrl("http://www.ama-assn.org/go/cpt"); + cs.setVersion(version); + cs.setName("AmaCPT"); + cs.setTitle("AMA CPT"); + cs.setStatus(PublicationStatus.ACTIVE); + cs.setDate(new Date()); + cs.setContent(CodeSystemContentMode.COMPLETE); + cs.setCompositional(true); + cs.setPublisher("AMA"); + cs.setValueSet("http://hl7.org/fhir/ValueSet/cpt-all"); + cs.setCopyright("CPT © Copyright 2019 American Medical Association. All rights reserved. AMA and CPT are registered trademarks of the American Medical Association."); + cs.addProperty().setCode("modifier").setDescription("Whether code is a modifier code").setType(PropertyType.BOOLEAN); + cs.addProperty().setCode("modified").setDescription("Whether code has been modified (all base codes are not modified)").setType(PropertyType.BOOLEAN); + cs.addProperty().setCode("kind").setDescription("Kind of Code (see metadata)").setType(PropertyType.CODE); + + defineMetadata(cs); + + System.out.println(readCodes(cs, Utilities.path(src, "LONGULT.txt"), false, null, null)); + System.out.println(readCodes(cs, Utilities.path(src, "LONGUT.txt"), false, "upper", null)); + System.out.println(readCodes(cs, Utilities.path(src, "MEDU.txt"), false, "med", null)); + System.out.println(readCodes(cs, Utilities.path(src, "SHORTU.txt"), false, "short", null)); + System.out.println(readCodes(cs, Utilities.path(src, "ConsumerDescriptor.txt"), true, "consumer", null)); + System.out.println(readCodes(cs, Utilities.path(src, "ClinicianDescriptor.txt"), true, "clinician", null)); + System.out.println(readCodes(cs, Utilities.path(src, "OrthopoxvirusCodes.txt"), false, null, "orthopod")); + + System.out.println(processModifiers(cs, Utilities.path(src, "modifiers.csv"))); + + System.out.println("-------------------"); + System.out.println(cs.getConcept().size()); + int c = 0; + int k = 0; + for (ConceptDefinitionComponent cc: cs.getConcept()) { + c = Integer.max(c, cc.getProperty().size()); + if (cc.getProperty().size() > 3) { + k++; + } + } + System.out.println(c); + System.out.println(k); + + new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(dst), cs); + + connect(Utilities.changeFileExt(dst, ".db")); + + Statement stmt = con.createStatement(); + stmt.execute("insert into Information (name, value) values ('version', "+cs.getVersion()+")"); + for (ConceptDefinitionComponent cc: cs.getConcept()) { + if (!cc.getCode().startsWith("metadata")) { + stmt.execute("insert into Concepts (code, modifier) values ('"+cc.getCode()+"', "+isModifier(cc)+")"); + int i = 0; + if (cc.hasDisplay()) { + stmt.execute("insert into Designations (code, type, sequence, value) values ('"+cc.getCode()+"', 0, 0, '"+Utilities.escapeSql(cc.getDisplay())+"')"); + i++; + } + for (ConceptDefinitionDesignationComponent d : cc.getDesignation()) { + stmt.execute("insert into Designations (code, type, sequence, value) values ('"+cc.getCode()+"', '"+d.getUse().getCode()+"', "+i+", '"+Utilities.escapeSql(d.getValue())+"')"); + i++; + } + i = 0; + for (ConceptPropertyComponent p : cc.getProperty()) { + if (!Utilities.existsInList(p.getCode(), "modified", "modifier")) { + stmt.execute("insert into Properties (code, name, sequence, value) values ('"+cc.getCode()+"', '"+p.getCode()+"', "+i+", '"+p.getValue().primitiveValue()+"')"); + i++; + } + } + } + } + cs.getConcept().removeIf(cc -> !Utilities.existsInList(cc.getCode(), "metadata-kinds", "metadata-designations", "99202", "25")); + + new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(dst+"-fragment"), cs); + } + + private String isModifier(ConceptDefinitionComponent cc) { + for (ConceptPropertyComponent p : cc.getProperty()) { + if (p.getCode().equals("modifier")) { + return p.getValue().primitiveValue().equals("true") ? "1" : "0"; + } + } + return "0"; + } + + + private void connect(String dest) throws SQLException, ClassNotFoundException { + // Class.forName("com.mysql.jdbc.Driver"); + // con = DriverManager.getConnection("jdbc:mysql://localhost:3306/omop?useSSL=false","root",{pwd}); + new File(dest).delete(); + con = DriverManager.getConnection("jdbc:sqlite:"+dest); + makeMetadataTable(); + makeConceptsTable(); + makeDesignationsTable(); + makePropertiesTable(); + + } + + private void makeDesignationsTable() throws SQLException { + Statement stmt = con.createStatement(); + stmt.execute("CREATE TABLE Designations (\r\n"+ + "`code` varchar(15) NOT NULL,\r\n"+ + "`type` varchar(15) NOT NULL,\r\n"+ + "`sequence` int NOT NULL,\r\n"+ + "`value` text NOT NULL,\r\n"+ + "PRIMARY KEY (`code`, `type`, `sequence`))\r\n"); + } + + + private void makePropertiesTable() throws SQLException { + + Statement stmt = con.createStatement(); + stmt.execute("CREATE TABLE Properties (\r\n"+ + "`code` varchar(15) NOT NULL,\r\n"+ + "`name` varchar(15) NOT NULL,\r\n"+ + "`sequence` int NOT NULL,\r\n"+ + "`value` varchar(15) NOT NULL,\r\n"+ + "PRIMARY KEY (`code`, `name`, `sequence`))\r\n"); + + } + + + private void makeConceptsTable() throws SQLException { + + Statement stmt = con.createStatement(); + stmt.execute("CREATE TABLE Concepts (\r\n"+ + "`code` varchar(15) NOT NULL,\r\n"+ + "`modifier` int DEFAULT NULL,\r\n"+ + "PRIMARY KEY (`code`))\r\n"); + + } + + + private void makeMetadataTable() throws SQLException { + + Statement stmt = con.createStatement(); + stmt.execute("CREATE TABLE Information (\r\n"+ + "`name` varchar(64) NOT NULL,\r\n"+ + "`value` varchar(64) DEFAULT NULL,\r\n"+ + "PRIMARY KEY (`name`))\r\n"); + + } + + + private void defineMetadata(CodeSystem cs) { + ConceptDefinitionComponent pc = mm(cs.addConcept().setCode("metadata-kinds")); + mm(pc.addConcept()).setCode("code").setDisplay("A normal CPT code"); + mm(pc.addConcept()).setCode("cat-1").setDisplay("CPT Level I Modifiers"); + mm(pc.addConcept()).setCode("cat-2").setDisplay("A Category II code or modifier"); + mm(pc.addConcept()).setCode("physical-status").setDisplay("Anesthesia Physical Status Modifiers"); + mm(pc.addConcept()).setCode("general").setDisplay("A general modifier"); + mm(pc.addConcept()).setCode("hcpcs").setDisplay("Level II (HCPCS/National) Modifiers"); + mm(pc.addConcept()).setCode("orthopox").setDisplay(""); + mm(pc.addConcept()).setCode("metadata").setDisplay("A kind of code or designation"); + + ConceptDefinitionComponent dc = mm(cs.addConcept().setCode("metadata-designations")); + mm(dc.addConcept()).setCode("upper").setDisplay("Uppercase variant of the display"); + mm(dc.addConcept()).setCode("med").setDisplay("Medium length variant of the display (all uppercase)"); + mm(dc.addConcept()).setCode("short").setDisplay("Short length variant of the display (all uppercase)"); + mm(dc.addConcept()).setCode("consumer").setDisplay("Consumer Friendly representation for the concept"); + mm(dc.addConcept()).setCode("clinician").setDisplay("Clinician Friendly representation for the concept (can be more than one per concept)"); + } + + private ConceptDefinitionComponent mm(ConceptDefinitionComponent cc) { + cc.addProperty().setCode("kind").setValue(new CodeType("metadata")); + return cc; + } + + private int processModifiers(CodeSystem cs, String path) throws FHIRException, FileNotFoundException, IOException { + CSVReader csv = new CSVReader(new FileInputStream(path)); + csv.readHeaders(); + + int res = 0; + while (csv.line()) { + String code = csv.cell("Code"); + String general = csv.cell("General"); + String physicalStatus = csv.cell("PhysicalStatus"); + String levelOne = csv.cell("LevelOne"); + String levelTwo = csv.cell("LevelTwo"); + String hcpcs = csv.cell("HCPCS"); + String defn = csv.cell("Definition"); + + res = Integer.max(res, defn.length()); + ConceptDefinitionComponent cc = cs.addConcept().setCode(code); + cc.setDisplay(defn); + cc.addProperty().setCode("modified").setValue(new BooleanType(false)); + cc.addProperty().setCode("modifier").setValue(new BooleanType(true)); + if ("1".equals(general)) { + cc.addProperty().setCode("kind").setValue(new CodeType("general")); + } + if ("1".equals(physicalStatus)) { + cc.addProperty().setCode("kind").setValue(new CodeType("physical-status")); + } + if ("1".equals(levelOne)) { + cc.addProperty().setCode("kind").setValue(new CodeType("cat-1")); + } + if ("1".equals(levelTwo)) { + cc.addProperty().setCode("kind").setValue(new CodeType("cat-2")); + } + if ("1".equals(hcpcs)) { + cc.addProperty().setCode("kind").setValue(new CodeType("hcpcs")); + } + } + return res; + } + + private int readCodes(CodeSystem cs, String path, boolean hasConceptId, String use, String type) throws IOException { + int res = 0; + FileInputStream inputStream = null; + Scanner sc = null; + try { + inputStream = new FileInputStream(path); + sc = new Scanner(inputStream, "UTF-8"); + while (sc.hasNextLine()) { + String line = sc.nextLine(); + if (hasConceptId) { + line = line.substring(7).trim(); + } + String code = line.substring(0, 5); + String desc = line.substring(6); + if (desc.contains("\t")) { + desc = desc.substring(desc.indexOf("\t")+1); + } + res = Integer.max(res, desc.length()); + ConceptDefinitionComponent cc = CodeSystemUtilities.getCode(cs, code); + if (cc == null) { + cc = cs.addConcept().setCode(code); + cc.addProperty().setCode("modifier").setValue(new BooleanType(false)); + cc.addProperty().setCode("modified").setValue(new BooleanType(false)); + if (type == null) { + if (Utilities.isInteger(code)) { + cc.addProperty().setCode("kind").setValue(new CodeType("code")); + } else { + cc.addProperty().setCode("kind").setValue(new CodeType("cat-2")); + } + } else { + cc.addProperty().setCode("kind").setValue(new CodeType(type)); + } + } else if (type != null) { + cc.addProperty().setCode("kind").setValue(new CodeType(type)); + } + if (use == null) { + if (cc.hasDisplay()) { + System.err.println("?"); + } + cc.setDisplay(desc); + } else { + cc.addDesignation().setUse(new Coding("http://www.ama-assn.org/go/cpt", use, null)).setValue(desc); + } + } + // note that Scanner suppresses exceptions + if (sc.ioException() != null) { + throw sc.ioException(); + } + } finally { + if (inputStream != null) { + inputStream.close(); + } + if (sc != null) { + sc.close(); + } + } + return res; + } + + +} diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml index a93dfb5db..982979400 100644 --- a/org.hl7.fhir.dstu2/pom.xml +++ b/org.hl7.fhir.dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.0.24-SNAPSHOT + 6.0.26-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu2016may/pom.xml b/org.hl7.fhir.dstu2016may/pom.xml index 7eac5a17c..14b6af817 100644 --- a/org.hl7.fhir.dstu2016may/pom.xml +++ b/org.hl7.fhir.dstu2016may/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.0.24-SNAPSHOT + 6.0.26-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml index 4829ed25a..de2223d63 100644 --- a/org.hl7.fhir.dstu3/pom.xml +++ b/org.hl7.fhir.dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.0.24-SNAPSHOT + 6.0.26-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml index 2d5b36252..00d596acb 100644 --- a/org.hl7.fhir.r4/pom.xml +++ b/org.hl7.fhir.r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.0.24-SNAPSHOT + 6.0.26-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r4b/pom.xml b/org.hl7.fhir.r4b/pom.xml index a525fd589..2cc0ac0cf 100644 --- a/org.hl7.fhir.r4b/pom.xml +++ b/org.hl7.fhir.r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.0.24-SNAPSHOT + 6.0.26-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml index 88cafa081..dd253fa57 100644 --- a/org.hl7.fhir.r5/pom.xml +++ b/org.hl7.fhir.r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.0.24-SNAPSHOT + 6.0.26-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/R5ExtensionsLoader.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/R5ExtensionsLoader.java index 1f1321470..a207c3b68 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/R5ExtensionsLoader.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/R5ExtensionsLoader.java @@ -192,13 +192,17 @@ public class R5ExtensionsLoader { for (CanonicalType t : inc.getValueSet()) { loadValueSet(t.asStringValue(), context, valueSets, codeSystems); } - if (inc.hasSystem() && !inc.hasVersion()) { - if (codeSystems.containsKey(inc.getSystem())) { - CodeSystem cs = codeSystems.get(inc.getSystem()).getResource(); - inc.setVersion(cs.getVersion()); - context.cacheResourceFromPackage(cs, cs.getSourcePackage()); - } else if (!context.hasResource(CodeSystem.class, inc.getSystem()) && codeSystems.containsKey(inc.getSystem())) { - CodeSystem cs1 = codeSystems.get(inc.getSystem()).getResource(); + if (inc.hasSystem()) { + if (!inc.hasVersion()) { + if (codeSystems.containsKey(inc.getSystem())) { + CodeSystem cs = codeSystems.get(inc.getSystem()).getResource(); + CodeSystem csAlready = context.fetchCodeSystem(inc.getSystem()); + if (csAlready == null) { + context.cacheResourceFromPackage(cs, cs.getSourcePackage()); + } + } + } else if (context.fetchResource(CodeSystem.class, inc.getSystem(), inc.getVersion()) == null && codeSystems.containsKey(inc.getSystem()+"|"+inc.getVersion())) { + CodeSystem cs1 = codeSystems.get(inc.getSystem()+"|"+inc.getVersion()).getResource(); context.cacheResourceFromPackage(cs1, cs1.getSourcePackage()); } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java index 62029741d..7d28f6601 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfilePathProcessor.java @@ -11,7 +11,9 @@ import org.hl7.fhir.r5.conformance.ElementRedirection; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; +import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; import org.hl7.fhir.r5.model.OperationOutcome.IssueType; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent; @@ -193,9 +195,16 @@ public class ProfilePathProcessor { private void debugProcessPathsIteration(ProfilePathProcessorState cursors, String currentBasePath) { if (profileUtilities.isDebug()) { - System.out.println(getDebugIndent() + " - " + currentBasePath + ": base = " + cursors.baseCursor + " (" + profileUtilities.descED(cursors.base.getElement(), cursors.baseCursor) + ") to " + getBaseLimit() + " (" + profileUtilities.descED(cursors.base.getElement(), getBaseLimit()) + "), diff = " + cursors.diffCursor + " (" + profileUtilities.descED(getDifferential().getElement(), cursors.diffCursor) + ") to " + getDiffLimit() + " (" + profileUtilities.descED(getDifferential().getElement(), getDiffLimit()) + ") " + + System.out.println(getDebugIndent() + " - " + currentBasePath + ": "+ + "base = " + cursors.baseCursor + " (" + profileUtilities.descED(cursors.base.getElement(), cursors.baseCursor) + ") to " + getBaseLimit() +" (" + profileUtilities.descED(cursors.base.getElement(), getBaseLimit()) + "), "+ + "diff = " + cursors.diffCursor + " (" + profileUtilities.descED(getDifferential().getElement(), cursors.diffCursor) + ") to " + getDiffLimit() + " (" + profileUtilities.descED(getDifferential().getElement(), getDiffLimit()) + ") " + "(slicingDone = " + getSlicing().isDone() + ") (diffpath= " + (getDifferential().getElement().size() > cursors.diffCursor ? getDifferential().getElement().get(cursors.diffCursor).getPath() : "n/a") + ")"); + String path = cursors.diffCursor >=0 && cursors.diffCursor < getDifferential().getElement().size() ? getDifferential().getElement().get(cursors.diffCursor).present() : null; +// if (path != null && path.contains(":populationBasis")) { +// System.out.println("!"); +// } } + } private void debugProcessPathsEntry(ProfilePathProcessorState cursors) { @@ -294,7 +303,7 @@ public class ProfilePathProcessor { // differential - if the first one in the list has a name, we'll process it. Else we'll treat it as the base definition of the slice. if (!diffMatches.get(0).hasSliceName()) { - profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), isTrimDifferential(), getUrl(),getSourceStructureDefinition(), getDerived()); + profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), isTrimDifferential(), getUrl(),getSourceStructureDefinition(), getDerived(), diffPath(diffMatches.get(0))); profileUtilities.removeStatusExtensions(outcome); if (!outcome.hasContentReference() && !outcome.hasType() && outcome.getPath().contains(".")) { throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.NOT_DONE_YET)); @@ -354,6 +363,10 @@ public class ProfilePathProcessor { cursors.diffCursor = newDiffLimit + 1; } + private String diffPath(ElementDefinition ed) { + return "StructureDefinition.differential.element["+differential.getElement().indexOf(ed)+"]"; + } + private String slicingSummary(ElementDefinitionSlicingComponent s) { return s.toString(); } @@ -635,6 +648,11 @@ public class ProfilePathProcessor { res = outcome; profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl()); if (diffMatches.get(0).hasSliceName()) { + template = currentBase.copy(); + template = profileUtilities.updateURLs(getUrl(), getWebUrl(), template); + template.setPath(profileUtilities.fixedPathDest(getContextPathTarget(), template.getPath(), getRedirector(), getContextPathSource())); + + checkToSeeIfSlicingExists(diffMatches.get(0), template); outcome.setSliceName(diffMatches.get(0).getSliceName()); if (!diffMatches.get(0).hasMin() && (diffMatches.size() > 1 || getSlicing().getElementDefinition()== null || getSlicing().getElementDefinition().getSlicing().getRules() != ElementDefinition.SlicingRules.CLOSED) && !currentBase.hasSliceName()) { if (!currentBasePath.endsWith("xtension.value[x]")) { // hack work around for problems with snapshots in official releases @@ -642,7 +660,7 @@ public class ProfilePathProcessor { } } } - profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived()); + profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived(), diffPath(diffMatches.get(0))); profileUtilities.removeStatusExtensions(outcome); // if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*") && !diffMatches.get(0).hasSlicing()) // if the base profile allows multiple types, but the profile only allows one, rename it // outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode())); @@ -743,6 +761,63 @@ public class ProfilePathProcessor { return res; } + private void checkToSeeIfSlicingExists(ElementDefinition ed, ElementDefinition template) { + List ss = result.getElement(); + int i = ss.size() -1; + ElementDefinition m = null; + + while (i >= 0) { + ElementDefinition t = ss.get(i); + if (pathsMatch(t.getPath(), ed.getPath())) { + if (t.hasSlicing() || t.hasSliceName() || t.getPath().endsWith("[x]")) { + m = t; + break; + } + } + if (t.getPath().length() < ed.getPath().length()) { + break; + } + i--; + } + if (m == null) { + if (template.getPath().endsWith(".extension")) { + template.getSlicing().setRules(SlicingRules.OPEN); + template.getSlicing().setOrdered(false); + template.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url"); + result.getElement().add(template); + } else { + System.err.println("checkToSeeIfSlicingExists: "+ed.getPath()+":"+ed.getSliceName()+" is not sliced"); + } + } + } + + private boolean pathsMatch(String path1, String path2) { + String[] p1 = path1.split("\\."); + String[] p2 = path2.split("\\."); + if (p1.length != p2.length) { + return false; + } + for (int i = 0; i < p1.length; i++) { + String pp1 = p1[i]; + String pp2 = p2[i]; + if (!pp1.equals(pp2)) { + if (pp1.endsWith("[x]")) { + if (!pp2.startsWith(pp1.substring(0, pp1.length()-3))) { + return false; + } + } else if (pp2.endsWith("[x]")) { + if (!pp1.startsWith(pp2.substring(0, pp2.length()-3))) { + return false; + } + + } else { + return false; + } + } + } + return true; + } + private int indexOfFirstNonChild(StructureDefinitionSnapshotComponent base, ElementDefinition currentBase, int i, int baseLimit) { return baseLimit+1; } @@ -937,7 +1012,7 @@ public class ProfilePathProcessor { profileUtilities.updateFromBase(outcome, currentBase, getSourceStructureDefinition().getUrl()); if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) { profileUtilities.updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing()); - profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), closed, getUrl(), getSourceStructureDefinition(), getDerived()); // if there's no slice, we don't want to update the unsliced description + profileUtilities.updateFromDefinition(outcome, diffMatches.get(0), getProfileName(), closed, getUrl(), getSourceStructureDefinition(), getDerived(), diffPath(diffMatches.get(0))); // if there's no slice, we don't want to update the unsliced description profileUtilities.removeStatusExtensions(outcome); } else if (!diffMatches.get(0).hasSliceName()) { diffMatches.get(0).setUserData(profileUtilities.UD_GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called @@ -1075,7 +1150,7 @@ public class ProfilePathProcessor { throw new DefinitionException(profileUtilities.getContext().formatMessage(I18nConstants.ADDING_WRONG_PATH)); debugCheck(outcome); getResult().getElement().add(outcome); - profileUtilities.updateFromDefinition(outcome, diffItem, getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived()); + profileUtilities.updateFromDefinition(outcome, diffItem, getProfileName(), isTrimDifferential(), getUrl(), getSourceStructureDefinition(), getDerived(), diffPath(diffItem)); profileUtilities.removeStatusExtensions(outcome); // --- LM Added this cursors.diffCursor = getDifferential().getElement().indexOf(diffItem) + 1; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java index 3b8f9d564..2128150d2 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java @@ -139,11 +139,13 @@ public class ProfileUtilities extends TranslatingUtilities { public class ElementDefinitionCounter { int countMin = 0; int countMax = 0; + int index = 0; ElementDefinition focus; Set names = new HashSet<>(); - public ElementDefinitionCounter(ElementDefinition ed) { + public ElementDefinitionCounter(ElementDefinition ed, int i) { focus = ed; + index = i; } public int checkMin() { @@ -192,6 +194,11 @@ public class ProfileUtilities extends TranslatingUtilities { public boolean checkMinMax() { return countMin <= countMax; } + + public int getIndex() { + return index; + } + } public enum MappingMergeModeOption { @@ -683,11 +690,12 @@ public class ProfileUtilities extends TranslatingUtilities { checkGroupConstraints(derived); if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) { + int i = 0; for (ElementDefinition e : diff.getElement()) { if (!e.hasUserData(UD_GENERATED_IN_SNAPSHOT) && e.getPath().contains(".")) { ElementDefinition existing = getElementInCurrentContext(e.getPath(), derived.getSnapshot().getElement()); if (existing != null) { - updateFromDefinition(existing, e, profileName, false, url, base, derived); + updateFromDefinition(existing, e, profileName, false, url, base, derived, "StructureDefinition.differential.element["+i+"]"); } else { ElementDefinition outcome = updateURLs(url, webUrl, e.copy()); e.setUserData(UD_GENERATED_IN_SNAPSHOT, outcome); @@ -701,6 +709,7 @@ public class ProfileUtilities extends TranslatingUtilities { } } } + i++; } } @@ -723,6 +732,7 @@ public class ProfileUtilities extends TranslatingUtilities { CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); //Check that all differential elements have a corresponding snapshot element int ce = 0; + int i = 0; for (ElementDefinition e : diff.getElement()) { if (!e.hasUserData("diff-source")) throw new Error(context.formatMessage(I18nConstants.UNXPECTED_INTERNAL_CONDITION__NO_SOURCE_ON_DIFF_ELEMENT)); @@ -736,15 +746,16 @@ public class ProfileUtilities extends TranslatingUtilities { b.append(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath()); ce++; if (e.hasId()) { - String msg = "No match found in the generated snapshot: check that the path and definitions are legal in the differential (including order)"; - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+e.getId(), msg, ValidationMessage.IssueSeverity.ERROR)); + String msg = "No match found for "+e.getId()+" in the generated snapshot: check that the path and definitions are legal in the differential (including order)"; + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, "StructureDefinition.differential.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR)); } } + i++; } if (!Utilities.noString(b.toString())) { String msg = "The profile "+derived.getUrl()+" has "+ce+" "+Utilities.pluralize("element", ce)+" in the differential ("+b.toString()+") that don't have a matching element in the snapshot: check that the path and definitions are legal in the differential (including order)"; if (debug) { - System.out.println("Error in snapshot generation: "+msg); + System.err.println("Error in snapshot generation: "+msg); if (!debug) { System.out.println("Differential: "); for (ElementDefinition ed : derived.getDifferential().getElement()) @@ -789,10 +800,10 @@ public class ProfileUtilities extends TranslatingUtilities { tn = tn.substring(tn.lastIndexOf("/")+1); } Map slices = new HashMap<>(); - int i = 0; + i = 0; for (ElementDefinition ed : derived.getSnapshot().getElement()) { if (ed.hasSlicing()) { - slices.put(ed.getPath(), new ElementDefinitionCounter(ed)); + slices.put(ed.getPath(), new ElementDefinitionCounter(ed, i)); } else { Set toRemove = new HashSet<>(); for (String s : slices.keySet()) { @@ -809,50 +820,57 @@ public class ProfileUtilities extends TranslatingUtilities { slice.getFocus().setMin(count); } else { String msg = "The slice definition for "+slice.getFocus().getId()+" has a minimum of "+slice.getFocus().getMin()+" but the slices add up to a minimum of "+count; - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+slice.getFocus().getId(), msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION).setIgnorableError(true)); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, + "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION).setIgnorableError(true)); } } count = slice.checkMax(); if (count > -1 && repeats) { String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" but the slices add up to a maximum of "+count+". Check that this is what is intended"; - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+slice.getFocus().getId(), msg, ValidationMessage.IssueSeverity.INFORMATION)); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, + "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.INFORMATION)); } if (!slice.checkMinMax()) { String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" which is less than the minimum of "+slice.getFocus().getMin(); - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+"#"+slice.getFocus().getId(), msg, ValidationMessage.IssueSeverity.WARNING)); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, + "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.WARNING)); } slices.remove(s); } } if (ed.getPath().contains(".") && !ed.getPath().startsWith(tn+".")) { - throw new Error("The element "+ed.getId()+" in the profile '"+derived.getVersionedUrl()+" (["+i+"]) doesn't have the right path (should start with "+tn+"."); + throw new Error("The element "+ed.getId()+" in the profile '"+derived.getVersionedUrl()+" doesn't have the right path (should start with "+tn+"."); } if (ed.hasSliceName() && !slices.containsKey(ed.getPath())) { - String msg = "The element "+ed.getId()+" (["+i+"]) launches straight into slicing without the slicing being set up properly first"; - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true)); + String msg = "The element "+ed.getId()+" launches straight into slicing without the slicing being set up properly first"; + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, + "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true)); } if (ed.hasSliceName() && slices.containsKey(ed.getPath())) { if (!slices.get(ed.getPath()).count(ed, ed.getSliceName())) { String msg = "Duplicate slice name "+ed.getSliceName()+" on "+ed.getId()+" (["+i+"])"; - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true)); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, + "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true)); } } i++; } + i = 0; // last, check for wrong profiles or target profiles for (ElementDefinition ed : derived.getSnapshot().getElement()) { for (TypeRefComponent t : ed.getType()) { for (UriType u : t.getProfile()) { StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue(), derived); if (sd == null) { - if (xver != null && xver.matchingUrl(u.getValue()) && xver.status(u.getValue()) == XVerExtensionStatus.Valid) { + if (makeXVer().matchingUrl(u.getValue()) && xver.status(u.getValue()) == XVerExtensionStatus.Valid) { sd = xver.makeDefinition(u.getValue()); } } if (sd == null) { if (messages != null) { - messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING)); + messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, + "StructureDefinition.snapshot.element["+i+"]", "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING)); } } else { String wt = t.getWorkingCode(); @@ -878,6 +896,7 @@ public class ProfileUtilities extends TranslatingUtilities { } } } + i++; } } catch (Exception e) { // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind @@ -891,6 +910,13 @@ public class ProfileUtilities extends TranslatingUtilities { } } + private XVerExtensionManager makeXVer() { + if (xver == null) { + xver = new XVerExtensionManager(context); + } + return xver; + } + private ElementDefinition getElementInCurrentContext(String path, List list) { for (int i = list.size() -1; i >= 0; i--) { ElementDefinition t = list.get(i); @@ -1358,7 +1384,7 @@ public class ProfileUtilities extends TranslatingUtilities { return true; } if (tr.getWorkingCode().equals(t.getCode())) { - System.out.println("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath()); + System.err.println("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath()); return true; } } @@ -1759,14 +1785,14 @@ public class ProfileUtilities extends TranslatingUtilities { if (type.hasProfile()) { sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue(), src); if (sd == null) { - if (xver != null && xver.matchingUrl(type.getProfile().get(0).getValue()) && xver.status(type.getProfile().get(0).getValue()) == XVerExtensionStatus.Valid) { + if (makeXVer().matchingUrl(type.getProfile().get(0).getValue()) && xver.status(type.getProfile().get(0).getValue()) == XVerExtensionStatus.Valid) { sd = xver.makeDefinition(type.getProfile().get(0).getValue()); generateSnapshot(context.fetchTypeDefinition("Extension"), sd, sd.getUrl(), webUrl, sd.getName()); } } if (sd == null) { if (debug) { - System.out.println("Failed to find referenced profile: " + type.getProfile()); + System.err.println("Failed to find referenced profile: " + type.getProfile()); } } @@ -1774,14 +1800,14 @@ public class ProfileUtilities extends TranslatingUtilities { if (sd == null) sd = context.fetchTypeDefinition(type.getWorkingCode()); if (sd == null) - System.out.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM + System.err.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM return sd; } protected StructureDefinition getProfileForDataType(String type) { StructureDefinition sd = context.fetchTypeDefinition(type); if (sd == null) - System.out.println("XX: failed to find profle for type: " + type); // debug GJM + System.err.println("XX: failed to find profle for type: " + type); // debug GJM return sd; } @@ -2252,7 +2278,7 @@ public class ProfileUtilities extends TranslatingUtilities { } - protected void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD, StructureDefinition derivedSrc) throws DefinitionException, FHIRException { + protected void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD, StructureDefinition derivedSrc, String path) throws DefinitionException, FHIRException { source.setUserData(UD_GENERATED_IN_SNAPSHOT, dest); // we start with a clone of the base profile ('dest') and we copy from the profile ('source') // over the top for anything the source has @@ -2297,8 +2323,25 @@ public class ProfileUtilities extends TranslatingUtilities { if (base.hasSliceName()) { profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue(), srcSD) : null; } - if (profile==null) { - profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue(), derivedSrc) : null; + if (profile == null && source.getTypeFirstRep().hasProfile()) { + String pu = source.getTypeFirstRep().getProfile().get(0).getValue(); + profile = context.fetchResource(StructureDefinition.class, pu, derivedSrc); + if (profile == null) { + if (makeXVer().matchingUrl(pu)) { + switch (xver.status(pu)) { + case BadVersion: + throw new FHIRException("Reference to invalid version in extension url " + pu); + case Invalid: + throw new FHIRException("Reference to invalid extension " + pu); + case Unknown: + throw new FHIRException("Reference to unknown extension " + pu); + case Valid: + profile = xver.makeDefinition(pu); + generateSnapshot(context.fetchTypeDefinition("Extension"), profile, profile.getUrl(), context.getSpecUrl(), profile.getName()); + } + } + + } if (profile != null && !"Extension".equals(profile.getType()) && profile.getKind() != StructureDefinitionKind.RESOURCE && profile.getKind() != StructureDefinitionKind.LOGICAL) { // this is a problem - we're kind of hacking things here. The problem is that we sometimes want the details from the profile to override the // inherited attributes, and sometimes not @@ -2666,7 +2709,7 @@ public class ProfileUtilities extends TranslatingUtilities { if (!Base.compareDeep(derived.getType(), base.getType(), false)) { if (base.hasType()) { for (TypeRefComponent ts : derived.getType()) { - checkTypeDerivation(purl, derivedSrc, base, derived, ts); + checkTypeDerivation(purl, derivedSrc, base, derived, ts, path); } } base.getType().clear(); @@ -2792,7 +2835,7 @@ public class ProfileUtilities extends TranslatingUtilities { } } - private void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts) { + private void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts, String path) { boolean ok = false; CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); String t = ts.getWorkingCode(); @@ -2814,13 +2857,14 @@ public class ProfileUtilities extends TranslatingUtilities { } } // work around for old badly generated SDs - if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) { - matchType = true; - } - if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) { - matchType = true; - } +// if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) { +// matchType = true; +// } +// if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) { +// matchType = true; +// } if (matchType) { + ts.copyExtensions(td, "http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support", "http://hl7.org/fhir/StructureDefinition/elementdefinition-pattern", "http://hl7.org/fhir/StructureDefinition/obligation"); if (ts.hasTargetProfile()) { // check that any derived target has a reference chain back to one of the base target profiles for (UriType u : ts.getTargetProfile()) { @@ -2830,7 +2874,7 @@ public class ProfileUtilities extends TranslatingUtilities { StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); if (sd == null) { if (messages != null) { - messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, purl + "#" + derived.getPath(), "Cannot check whether the target profile " + url + " is valid constraint on the base because it is not known", IssueSeverity.WARNING)); + messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, "Cannot check whether the target profile " + url + " on "+derived.getPath()+" is valid constraint on the base because it is not known", IssueSeverity.WARNING)); } url = null; tgtOk = true; // suppress error message @@ -2839,9 +2883,9 @@ public class ProfileUtilities extends TranslatingUtilities { tgtOk = td.hasTargetProfile(url); } } - if (tgtOk) + if (tgtOk) { ok = true; - else { + } else { if (messages == null) { throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT__THE_TARGET_PROFILE__IS_NOT__VALID_CONSTRAINT_ON_THE_BASE_, purl, derived.getPath(), url, td.getTargetProfile())); } else { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java index 13d21dd32..2962e8908 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java @@ -1490,6 +1490,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte String system = null; String code = null; String version = null; + List issues = new ArrayList<>(); + TerminologyServiceErrorClass err = TerminologyServiceErrorClass.UNKNOWN; for (ParametersParameterComponent p : pOut.getParameter()) { if (p.hasValue()) { @@ -1506,7 +1508,19 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } else if (p.getName().equals("code")) { code = ((PrimitiveType) p.getValue()).asStringValue(); } else if (p.getName().equals("x-caused-by-unknown-system")) { - err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED; + err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED; + } else if (p.getName().equals("warning-withdrawn")) { + OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXPIRED); + iss.getDetails().setText(formatMessage(I18nConstants.MSG_WITHDRAWN, ((PrimitiveType) p.getValue()).asStringValue())); + issues.add(iss); + } else if (p.getName().equals("warning-deprecated")) { + OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXPIRED); + iss.getDetails().setText(formatMessage(I18nConstants.MSG_DEPRECATED, ((PrimitiveType) p.getValue()).asStringValue())); + issues.add(iss); + } else if (p.getName().equals("warning-retired")) { + OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXPIRED); + iss.getDetails().setText(formatMessage(I18nConstants.MSG_RETIRED, ((PrimitiveType) p.getValue()).asStringValue())); + issues.add(iss); } else if (p.getName().equals("cause")) { try { IssueType it = IssueType.fromCode(((StringType) p.getValue()).getValue()); @@ -1524,15 +1538,18 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } } } + ValidationResult res = null; if (!ok) { - return new ValidationResult(IssueSeverity.ERROR, message+" (from "+tcc.getClient().getId()+")", err, null).setTxLink(txLog.getLastId()); + res = new ValidationResult(IssueSeverity.ERROR, message+" (from "+tcc.getClient().getId()+")", err, null).setTxLink(txLog.getLastId()); } else if (message != null && !message.equals("No Message returned")) { - return new ValidationResult(IssueSeverity.WARNING, message+" (from "+tcc.getClient().getId()+")", system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display, null).setTxLink(txLog.getLastId()); + res = new ValidationResult(IssueSeverity.WARNING, message+" (from "+tcc.getClient().getId()+")", system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display, null).setTxLink(txLog.getLastId()); } else if (display != null) { - return new ValidationResult(system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display).setTxLink(txLog.getLastId()); + res = new ValidationResult(system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display).setTxLink(txLog.getLastId()); } else { - return new ValidationResult(system, version, new ConceptDefinitionComponent().setCode(code), null).setTxLink(txLog.getLastId()); + res = new ValidationResult(system, version, new ConceptDefinitionComponent().setCode(code), null).setTxLink(txLog.getLastId()); } + res.setIssues(issues ); + return res; } // -------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java index 1bec15484..49f4b1114 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java @@ -124,7 +124,7 @@ public interface IWorkerContext { private List issues = new ArrayList<>(); private CodeableConcept codeableConcept; private Set unknownSystems; - + @Override public String toString() { return "ValidationResult [definition=" + definition + ", system=" + system + ", severity=" + severity + ", message=" + message + ", errorClass=" @@ -315,6 +315,14 @@ public interface IWorkerContext { } } + public void setIssues(List issues) { + if (this.issues != null) { + issues.addAll(this.issues); + } + this.issues = issues; + + } + } public class CodingValidationRequest { 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 62a8ac0c6..ba5a5dca9 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 @@ -544,8 +544,9 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon if (s.startsWith("version=")) { if (version == null) version = s.substring(8); - else if (!version.equals(s.substring(8))) + else if (!version.equals(s.substring(8))) { throw new DefinitionException(formatMessage(I18nConstants.VERSION_MISMATCH_THE_CONTEXT_HAS_VERSION__LOADED_AND_THE_NEW_CONTENT_BEING_LOADED_IS_VERSION_, version, s.substring(8))); + } } if (s.startsWith("revision=")) revision = s.substring(9); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TerminologyCache.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TerminologyCache.java index 7254b80f6..4d574c3c4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TerminologyCache.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TerminologyCache.java @@ -695,10 +695,7 @@ public class TerminologyCache { } protected String hashJson(String s) { - s = StringUtils.remove(s, ' '); - s = StringUtils.remove(s, '\n'); - s = StringUtils.remove(s, '\r'); - return String.valueOf(s.hashCode()); + return String.valueOf(s.trim().hashCode()); } // management diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Element.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Element.java index 5f089b233..0df5a0532 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Element.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Element.java @@ -498,6 +498,14 @@ public abstract class Element extends Base implements IBaseHasExtensions, IBaseE } } + public void copyNewExtensions(org.hl7.fhir.r5.model.Element src, String... urls) { + for (Extension e : src.getExtension()) { + if (Utilities.existsInList(e.getUrl(), urls) && !!hasExtension(e.getUrl())) { + addExtension(e.copy()); + } + } + } + // end addition diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ElementDefinition.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ElementDefinition.java index 13596d826..0f1556cf4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ElementDefinition.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ElementDefinition.java @@ -12748,6 +12748,15 @@ If a pattern[x] is declared on a repeating element, the pattern applies to all r } return b.toString(); } + + public List typeList() { + List res = new ArrayList<>(); + for (TypeRefComponent tr : getType()) { + if (tr.hasCode()) + res.add(tr.getWorkingCode()); + } + return res; + } public String typeSummaryVB() { CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("|"); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Parameters.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Parameters.java index ac42902f3..0d673f2e5 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Parameters.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Parameters.java @@ -1753,6 +1753,14 @@ public String toString() { return true; } return false; + } + + public boolean hasParameterValue(String name, String value) { + for (ParametersParameterComponent p : getParameter()) { + if (p.getName().equals(name) && p.hasValue() && value.equals(p.getValue().primitiveValue())) + return true; + } + return false; } public boolean hasParameter(String name) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ValueSet.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ValueSet.java index 35c721aeb..ccf8106cd 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ValueSet.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ValueSet.java @@ -2839,12 +2839,24 @@ public class ValueSet extends MetadataResource { , total, offset, parameter, property, contains); } - public String fhirType() { - return "ValueSet.expansion"; + public boolean hasParameterValue(String name, String value) { + for (ValueSetExpansionParameterComponent p : getParameter()) { + if (name.equals(p.getName()) && p.hasValue() && value.equals(p.getValue().primitiveValue())) { + return true; + } + } + return false; + } - } + public void addParameter(String name, DataType value) { + getParameter().add(new ValueSetExpansionParameterComponent(name).setValue(value)); + } + + public String fhirType() { + return "ValueSet.expansion"; + } - } + } @Block() public static class ValueSetExpansionParameterComponent extends BackboneElement implements IBaseBackboneElement { 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 308b7410c..6e1269d8c 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 @@ -198,7 +198,6 @@ public class ValueSetRenderer extends TerminologyRenderer { generateContentModeNotices(x, vs.getExpansion(), vs); generateVersionNotice(x, vs.getExpansion(), vs); - CodeSystem allCS = null; boolean doLevel = false; for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { if (cc.hasContains()) { @@ -206,8 +205,9 @@ public class ValueSetRenderer extends TerminologyRenderer { break; } } - + boolean doInactive = checkDoInactive(vs.getExpansion().getContains()); boolean doDefinition = checkDoDefinition(vs.getExpansion().getContains()); + XhtmlNode t = x.table( "codes"); XhtmlNode tr = t.tr(); if (doLevel) @@ -221,6 +221,9 @@ public class ValueSetRenderer extends TerminologyRenderer { scanForDesignations(c, langs, designations); } scanForProperties(vs.getExpansion(), langs, properties); + if (doInactive) { + tr.td().b().tx("Inactive"); + } if (doDefinition) { tr.td().b().tx("Definition"); doDesignations = false; @@ -250,7 +253,7 @@ public class ValueSetRenderer extends TerminologyRenderer { addMapHeaders(tr, maps); for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { - addExpansionRowToTable(t, vs, c, 1, doLevel, true, doDefinition, maps, allCS, langs, designations, doDesignations, properties); + addExpansionRowToTable(t, vs, c, 1, doLevel, doDefinition, doInactive, maps, langs, designations, doDesignations, properties); } // now, build observed languages @@ -673,6 +676,17 @@ public class ValueSetRenderer extends TerminologyRenderer { return false; } + private boolean checkDoInactive(List contains) { + for (ValueSetExpansionContainsComponent c : contains) { + if (c.hasInactive()) { + return true; + } + if (checkDoInactive(c.getContains())) + return true; + } + return false; + } + private boolean allFromOneSystem(ValueSet vs) { if (vs.getExpansion().getContains().isEmpty()) @@ -754,7 +768,7 @@ public class ValueSetRenderer extends TerminologyRenderer { } } - private void addExpansionRowToTable(XhtmlNode t, ValueSet vs, ValueSetExpansionContainsComponent c, int i, boolean doLevel, boolean doSystem, boolean doDefinition, List maps, CodeSystem allCS, List langs, Map designations, boolean doDesignations, Map properties) throws FHIRFormatError, DefinitionException, IOException { + private void addExpansionRowToTable(XhtmlNode t, ValueSet vs, ValueSetExpansionContainsComponent c, int i, boolean doLevel, boolean doDefinition, boolean doInactive, List maps, List langs, Map designations, boolean doDesignations, Map properties) throws FHIRFormatError, DefinitionException, IOException { XhtmlNode tr = t.tr(); if (ValueSetUtilities.isDeprecated(vs, c)) { tr.setAttribute("style", "background-color: #ffeeee"); @@ -772,19 +786,21 @@ public class ValueSetRenderer extends TerminologyRenderer { String s = Utilities.padLeft("", '\u00A0', i*2); td.attribute("style", "white-space:nowrap").addText(s); addCodeToTable(c.getAbstract(), c.getSystem(), c.getCode(), c.getDisplay(), td); - if (doSystem) { - td = tr.td(); - td.addText(c.getSystem()); - } + td = tr.td(); + td.addText(c.getSystem()); td = tr.td(); if (c.hasDisplayElement()) td.addText(c.getDisplay()); - if (doDefinition) { - CodeSystem cs = allCS; - if (cs == null) - cs = getContext().getWorker().fetchCodeSystem(c.getSystem()); + if (doInactive) { td = tr.td(); + if (c.getInactive()) { + td.tx("inactive"); + } + } + if (doDefinition) { + td = tr.td(); + CodeSystem cs = getContext().getWorker().fetchCodeSystem(c.getSystem()); if (cs != null) { String defn = CodeSystemUtilities.getCodeDefinition(cs, c.getCode()); addMarkdown(td, defn, cs.getWebPath()); @@ -817,7 +833,7 @@ public class ValueSetRenderer extends TerminologyRenderer { addLangaugesToRow(c, langs, tr); } for (ValueSetExpansionContainsComponent cc : c.getContains()) { - addExpansionRowToTable(t, vs, cc, i+1, doLevel, doSystem, doDefinition, maps, allCS, langs, designations, doDesignations, properties); + addExpansionRowToTable(t, vs, cc, i+1, doLevel, doDefinition, doInactive, maps, langs, designations, doDesignations, properties); } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java index 687e385bb..098fd9f5d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java @@ -369,6 +369,10 @@ public class CodeSystemUtilities { if ("inactive".equals(p.getCode()) && p.hasValueBooleanType()) { return p.getValueBooleanType().getValue(); } + if ("inactive".equals(p.getCode()) && p.hasValueCodeType()) { + String code = p.getValueCodeType().primitiveValue(); + return "true".equals(code); + } } return false; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/expansion/ValueSetExpander.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/expansion/ValueSetExpander.java index c998a4074..8f5d6e3af 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/expansion/ValueSetExpander.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/expansion/ValueSetExpander.java @@ -124,7 +124,6 @@ public class ValueSetExpander extends ValueSetProcessBase { private static final boolean REPORT_VERSION_ANYWAY = true; - private IWorkerContext context; private ValueSet focus; private List allErrors = new ArrayList<>(); private List requiredSupplements = new ArrayList<>(); @@ -151,9 +150,9 @@ public class ValueSetExpander extends ValueSetProcessBase { private ValueSetExpansionContainsComponent addCode(WorkingContext wc, String system, String code, String display, String dispLang, ValueSetExpansionContainsComponent parent, List designations, Parameters expParams, boolean isAbstract, boolean inactive, String definition, List filters, boolean noInactive, boolean deprecated, List vsProp, - List csProps, List expProps, List csExtList, List vsExtList) throws ETooCostly { + List csProps, List expProps, List csExtList, List vsExtList, ValueSetExpansionComponent exp) throws ETooCostly { - if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code)) + if (filters != null && !filters.isEmpty() && !filterContainsCode(filters, system, code, exp)) return null; if (noInactive && inactive) { return null; @@ -285,10 +284,12 @@ public class ValueSetExpander extends ValueSetProcessBase { return n; } - private boolean filterContainsCode(List filters, String system, String code) { - for (ValueSet vse : filters) + private boolean filterContainsCode(List filters, String system, String code, ValueSetExpansionComponent exp) { + for (ValueSet vse : filters) { + checkCanonical(exp, vse); if (expansionContainsCode(vse.getExpansion().getContains(), system, code)) return true; + } return false; } @@ -312,18 +313,18 @@ public class ValueSetExpander extends ValueSetProcessBase { return null; } - private void addCodeAndDescendents(WorkingContext wc, ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List filters, boolean noInactive, List vsProps, ValueSet vsSrc) throws FHIRException, ETooCostly { + private void addCodeAndDescendents(WorkingContext wc, ValueSetExpansionContainsComponent focus, ValueSetExpansionContainsComponent parent, Parameters expParams, List filters, boolean noInactive, List vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { focus.checkNoModifiers("Expansion.contains", "expanding"); ValueSetExpansionContainsComponent np = null; for (String code : getCodesForConcept(focus, expParams)) { ValueSetExpansionContainsComponent t = addCode(wc, focus.getSystem(), code, focus.getDisplay(), vsSrc.getLanguage(), parent, - convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), focus.getExtensionString(ToolingExtensions.EXT_DEFINITION), filters, noInactive, false, vsProps, null, focus.getProperty(), null, focus.getExtension()); + convert(focus.getDesignation()), expParams, focus.getAbstract(), focus.getInactive(), focus.getExtensionString(ToolingExtensions.EXT_DEFINITION), filters, noInactive, false, vsProps, null, focus.getProperty(), null, focus.getExtension(), exp); if (np == null) { np = t; } } for (ValueSetExpansionContainsComponent c : focus.getContains()) - addCodeAndDescendents(wc, c, np, expParams, filters, noInactive, vsProps, vsSrc); + addCodeAndDescendents(wc, c, np, expParams, filters, noInactive, vsProps, vsSrc, exp); } private List getCodesForConcept(ValueSetExpansionContainsComponent focus, Parameters expParams) { @@ -349,8 +350,8 @@ public class ValueSetExpander extends ValueSetProcessBase { return list; } - private void addCodeAndDescendents(WorkingContext wc,CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List filters, - ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, boolean noInactive, List vsProps, List otherFilters) throws FHIRException, ETooCostly { + private void addCodeAndDescendents(WorkingContext wc, CodeSystem cs, String system, ConceptDefinitionComponent def, ValueSetExpansionContainsComponent parent, Parameters expParams, List filters, + ConceptDefinitionComponent exclusion, ConceptFilter filterFunc, boolean noInactive, List vsProps, List otherFilters, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { def.checkNoModifiers("Code in Code System", "expanding"); if (exclusion != null) { if (exclusion.getCode().equals(def.getCode())) @@ -362,19 +363,19 @@ public class ValueSetExpander extends ValueSetProcessBase { boolean dep = CodeSystemUtilities.isDeprecated(cs, def, false); if ((includeAbstract || !abs) && filterFunc.includeConcept(cs, def) && passesOtherFilters(otherFilters, cs, def.getCode())) { for (String code : getCodesForConcept(def, expParams)) { - ValueSetExpansionContainsComponent t = addCode(wc, system, code, def.getDisplay(), cs.getLanguage(), parent, def.getDesignation(), expParams, abs, inc, def.getDefinition(), filters, noInactive, dep, vsProps, def.getProperty(), null, def.getExtension(), null); + ValueSetExpansionContainsComponent t = addCode(wc, system, code, def.getDisplay(), cs.getLanguage(), parent, def.getDesignation(), expParams, abs, inc, def.getDefinition(), filters, noInactive, dep, vsProps, def.getProperty(), null, def.getExtension(), null, exp); if (np == null) { np = t; } } } for (ConceptDefinitionComponent c : def.getConcept()) { - addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters); + addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp); } if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { List children = (List) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); for (ConceptDefinitionComponent c : children) - addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters); + addCodeAndDescendents(wc, cs, system, c, np, expParams, filters, exclusion, filterFunc, noInactive, vsProps, otherFilters, exp); } } @@ -401,7 +402,7 @@ public class ValueSetExpander extends ValueSetProcessBase { - private void addCodes(ValueSetExpansionComponent expand, List params, Parameters expParams, List filters, boolean noInactive, List vsProps, ValueSet vsSrc) throws ETooCostly, FHIRException { + private void addCodes(ValueSetExpansionComponent expand, List params, Parameters expParams, List filters, boolean noInactive, List vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws ETooCostly, FHIRException { if (expand != null) { if (expand.getContains().size() > maxExpansionSize) throw failCostly(context.formatMessage(I18nConstants.VALUESET_TOO_COSTLY, vsSrc.getUrl(), ">" + Integer.toString(expand.getContains().size()))); @@ -410,7 +411,7 @@ public class ValueSetExpander extends ValueSetProcessBase { params.add(p); } - copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps, vsSrc); + copyImportContains(expand.getContains(), null, expParams, filters, noInactive, vsProps, vsSrc, exp); } } @@ -450,8 +451,6 @@ public class ValueSetExpander extends ValueSetProcessBase { throw fail("not done yet - multiple filters"); } - - private void excludeCodes(WorkingContext wc, ValueSetExpansionComponent expand, List params) { for (ValueSetExpansionContainsComponent c : expand.getContains()) { excludeCode(wc, c.getSystem(), c.getCode()); @@ -460,8 +459,9 @@ public class ValueSetExpander extends ValueSetProcessBase { private boolean existsInParams(List params, String name, DataType value) { for (ValueSetExpansionParameterComponent p : params) { - if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) + if (p.getName().equals(name) && PrimitiveType.compareDeep(p.getValue(), value, false)) { return true; + } } return false; } @@ -503,6 +503,7 @@ public class ValueSetExpander extends ValueSetProcessBase { focus.setExpansion(new ValueSet.ValueSetExpansionComponent()); focus.getExpansion().setTimestampElement(DateTimeType.now()); focus.getExpansion().setIdentifier(Factory.createUUID()); + checkCanonical(focus.getExpansion(), focus); for (ParametersParameterComponent p : expParams.getParameter()) { if (Utilities.existsInList(p.getName(), "includeDesignations", "excludeNested", "activeOnly", "offset", "count")) { focus.getExpansion().addParameter().setName(p.getName()).setValue(p.getValue()); @@ -642,6 +643,7 @@ public class ValueSetExpander extends ValueSetProcessBase { throw fail("Unable to find imported value set " + value); } } + checkCanonical(exp, vs); if (noInactive) { expParams = expParams.copy(); expParams.addParameter("activeOnly", true); @@ -714,12 +716,12 @@ public class ValueSetExpander extends ValueSetProcessBase { } } - private void copyImportContains(List list, ValueSetExpansionContainsComponent parent, Parameters expParams, List filter, boolean noInactive, List vsProps, ValueSet vsSrc) throws FHIRException, ETooCostly { + private void copyImportContains(List list, ValueSetExpansionContainsComponent parent, Parameters expParams, List filter, boolean noInactive, List vsProps, ValueSet vsSrc, ValueSetExpansionComponent exp) throws FHIRException, ETooCostly { for (ValueSetExpansionContainsComponent c : list) { c.checkNoModifiers("Imported Expansion in Code System", "expanding"); ValueSetExpansionContainsComponent np = addCode(dwc, c.getSystem(), c.getCode(), c.getDisplay(), vsSrc.getLanguage(), parent, null, expParams, c.getAbstract(), c.getInactive(), c.getExtensionString(ToolingExtensions.EXT_DEFINITION), - filter, noInactive, false, vsProps, null, c.getProperty(), null, c.getExtension()); - copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc); + filter, noInactive, false, vsProps, null, c.getProperty(), null, c.getExtension(), exp); + copyImportContains(c.getContains(), np, expParams, filter, noInactive, vsProps, vsSrc, exp); } } @@ -734,9 +736,10 @@ public class ValueSetExpander extends ValueSetProcessBase { if (imports.isEmpty()) // though this is not supposed to be the case return; ValueSet base = imports.get(0); + checkCanonical(exp, base); imports.remove(0); base.checkNoModifiers("Imported ValueSet", "expanding"); - copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base); + copyImportContains(base.getExpansion().getContains(), null, expParams, imports, noInactive, base.getExpansion().getProperty(), base, exp); } else { CodeSystem cs = context.fetchSupplementedCodeSystem(inc.getSystem()); if (ValueSetUtilities.isServerSide(inc.getSystem()) || (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT))) { @@ -783,7 +786,7 @@ public class ValueSetExpander extends ValueSetProcessBase { } } for (ValueSetExpansionContainsComponent cc : vs.getExpansion().getContains()) { - addCodeAndDescendents(dwc, cc, null, expParams, imports, noInactive, vsProps, vs); + addCodeAndDescendents(dwc, cc, null, expParams, imports, noInactive, vsProps, vs, exp); } } @@ -795,6 +798,7 @@ public class ValueSetExpander extends ValueSetProcessBase { else throw failTSE("Unable to find code system " + inc.getSystem().toString()); } + checkCanonical(exp, cs); cs.checkNoModifiers("Code System", "expanding"); if (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT) throw failTSE("Code system " + inc.getSystem().toString() + " is incomplete"); @@ -813,7 +817,7 @@ public class ValueSetExpander extends ValueSetProcessBase { if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) { // special case - add all the code system for (ConceptDefinitionComponent def : cs.getConcept()) { - addCodeAndDescendents(dwc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), null); + addCodeAndDescendents(dwc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), null, exp); } if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { addFragmentWarning(exp, cs); @@ -846,7 +850,7 @@ public class ValueSetExpander extends ValueSetProcessBase { isAbstract = CodeSystemUtilities.isNotSelectable(cs, def); } addCode(dwc, inc.getSystem(), c.getCode(), !Utilities.noString(c.getDisplay()) ? c.getDisplay() : def == null ? null : def.getDisplay(), c.hasDisplay() ? vsSrc.getLanguage() : cs.getLanguage(), null, mergeDesignations(def, convertDesignations(c.getDesignation())), - expParams, isAbstract, inactive, def == null ? null : def.getDefinition(), imports, noInactive, false, exp.getProperty(), def != null ? def.getProperty() : null, null, def == null ? null : def.getExtension(), c.getExtension()); + expParams, isAbstract, inactive, def == null ? null : def.getDefinition(), imports, noInactive, false, exp.getProperty(), def != null ? def.getProperty() : null, null, def == null ? null : def.getExtension(), c.getExtension(), exp); } } if (inc.getFilter().size() > 0) { @@ -876,14 +880,14 @@ public class ValueSetExpander extends ValueSetProcessBase { ConceptDefinitionComponent def = getConceptForCode(cs.getConcept(), fc.getValue()); if (def == null) throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); - addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters); + addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.ISNOTA) { // special: all codes in the target code system that are not under the value ConceptDefinitionComponent defEx = getConceptForCode(cs.getConcept(), fc.getValue()); if (defEx == null) throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); for (ConceptDefinitionComponent def : cs.getConcept()) { - addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, defEx, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters); + addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, defEx, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); } } else if ("concept".equals(fc.getProperty()) && fc.getOp() == FilterOperator.DESCENDENTOF) { // special: all codes in the target code system under the value @@ -891,11 +895,11 @@ public class ValueSetExpander extends ValueSetProcessBase { if (def == null) throw failTSE("Code '" + fc.getValue() + "' not found in system '" + inc.getSystem() + "'"); for (ConceptDefinitionComponent c : def.getConcept()) - addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters); + addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); if (def.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { List children = (List) def.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); for (ConceptDefinitionComponent c : children) - addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters); + addCodeAndDescendents(wc, cs, inc.getSystem(), c, null, expParams, imports, null, new AllConceptsFilter(allErrors), noInactive, exp.getProperty(), filters, exp); } } else if ("display".equals(fc.getProperty()) && fc.getOp() == FilterOperator.EQUAL) { @@ -907,18 +911,18 @@ public class ValueSetExpander extends ValueSetProcessBase { if (def.getDisplay().contains(fc.getValue()) && passesOtherFilters(filters, cs, def.getCode())) { for (String code : getCodesForConcept(def, expParams)) { ValueSetExpansionContainsComponent t = addCode(wc, inc.getSystem(), code, def.getDisplay(), cs.getLanguage(), null, def.getDesignation(), expParams, CodeSystemUtilities.isNotSelectable(cs, def), CodeSystemUtilities.isInactive(cs, def), - def.getDefinition(), imports, noInactive, false, exp.getProperty(), def.getProperty(), null, def.getExtension(), null); + def.getDefinition(), imports, noInactive, false, exp.getProperty(), def.getProperty(), null, def.getExtension(), null, exp); } } } } } else if (isDefinedProperty(cs, fc.getProperty())) { for (ConceptDefinitionComponent def : cs.getConcept()) { - addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new PropertyFilter(allErrors, fc, getPropertyDefinition(cs, fc.getProperty())), noInactive, exp.getProperty(), filters); + addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new PropertyFilter(allErrors, fc, getPropertyDefinition(cs, fc.getProperty())), noInactive, exp.getProperty(), filters, exp); } } else if ("code".equals(fc.getProperty()) && fc.getOp() == FilterOperator.REGEX) { for (ConceptDefinitionComponent def : cs.getConcept()) { - addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new RegexFilter(allErrors, fc.getValue()), noInactive, exp.getProperty(), filters); + addCodeAndDescendents(wc, cs, inc.getSystem(), def, null, expParams, imports, null, new RegexFilter(allErrors, fc.getValue()), noInactive, exp.getProperty(), filters, exp); } } else { throw fail("Filter by property[" + fc.getProperty() + "] and op[" + fc.getOp() + "] is not supported yet"); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/ValueSetProcessBase.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/ValueSetProcessBase.java index 30de48170..567556b45 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/ValueSetProcessBase.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/ValueSetProcessBase.java @@ -1,20 +1,31 @@ package org.hl7.fhir.r5.terminologies.utilities; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.CanonicalResource; +import org.hl7.fhir.r5.model.DataType; +import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r5.model.OperationOutcome.IssueType; +import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent; import org.hl7.fhir.r5.model.Extension; import org.hl7.fhir.r5.model.Parameters; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.model.BooleanType; -import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; -import org.hl7.fhir.r5.model.DataType; import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; -import org.hl7.fhir.r5.model.PrimitiveType; +import org.hl7.fhir.r5.model.UriType; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.r5.utils.ToolingExtensions; +import org.hl7.fhir.utilities.StandardsStatus; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.i18n.I18nConstants; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; public class ValueSetProcessBase { + protected IWorkerContext context; + public static class AlternateCodesProcessingRules { private boolean all; private List uses = new ArrayList<>(); @@ -77,6 +88,88 @@ public class ValueSetProcessBase { } } + + protected List makeIssue(IssueSeverity level, IssueType type, String location, String message) { + OperationOutcomeIssueComponent result = new OperationOutcomeIssueComponent(); + switch (level) { + case ERROR: + result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR); + break; + case FATAL: + result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.FATAL); + break; + case INFORMATION: + result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION); + break; + case WARNING: + result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING); + break; + } + result.setCode(type); + result.addLocation(location); + result.getDetails().setText(message); + ArrayList list = new ArrayList<>(); + list.add(result); + return list; + } + + public void checkCanonical(List issues, String path, CanonicalResource resource) { + if (resource != null) { + StandardsStatus standardsStatus = ToolingExtensions.getStandardsStatus(resource); + if (standardsStatus == StandardsStatus.DEPRECATED) { + addToIssues(issues, makeStatusIssue(path, "deprecated", I18nConstants.MSG_DEPRECATED, resource)); + } else if (standardsStatus == StandardsStatus.WITHDRAWN) { + addToIssues(issues, makeStatusIssue(path, "withdrawn", I18nConstants.MSG_WITHDRAWN, resource)); + } else if (resource.getStatus() == PublicationStatus.RETIRED) { + addToIssues(issues, makeStatusIssue(path, "retired", I18nConstants.MSG_RETIRED, resource)); + } + } + } + + private List makeStatusIssue(String path, String id, String msg, CanonicalResource resource) { + List iss = makeIssue(IssueSeverity.INFORMATION, IssueType.EXPIRED, path, context.formatMessage(msg, resource.getVersionedUrl())); + + // this is a testing hack - see TerminologyServiceTests + iss.get(0).setUserData("status-msg-name", "warning-"+id); + iss.get(0).setUserData("status-msg-value", new UriType(resource.getVersionedUrl())); + + return iss; + } + + private void addToIssues(List issues, List toAdd) { + for (OperationOutcomeIssueComponent t : toAdd) { + boolean found = false; + for (OperationOutcomeIssueComponent i : issues) { + if (i.getSeverity() == t.getSeverity() && i.getCode() == t.getCode() && i.getDetails().getText().equals(t.getDetails().getText())) { // ignore location + found = true; + } + } + if (!found) { + issues.add(t); + } + } + } + + public void checkCanonical(ValueSetExpansionComponent params, CanonicalResource resource) { + if (resource != null) { + StandardsStatus standardsStatus = ToolingExtensions.getStandardsStatus(resource); + if (standardsStatus == StandardsStatus.DEPRECATED) { + if (!params.hasParameterValue("warning-deprecated", resource.getVersionedUrl())) { + params.addParameter("warning-deprecated", new UriType(resource.getVersionedUrl())); + } + } else if (standardsStatus == StandardsStatus.WITHDRAWN) { + if (!params.hasParameterValue("warning-withdrawn", resource.getVersionedUrl())) { + params.addParameter("warning-withdrawn", new UriType(resource.getVersionedUrl())); + } + } else if (resource.getStatus() == PublicationStatus.RETIRED) { + if (!params.hasParameterValue("warning-retired", resource.getVersionedUrl())) { + params.addParameter("warning-retired", new UriType(resource.getVersionedUrl())); + } + } + } + } + + protected AlternateCodesProcessingRules altCodeParams = new AlternateCodesProcessingRules(false); protected AlternateCodesProcessingRules allAltCodes = new AlternateCodesProcessingRules(true); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValidationProcessInfo.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValidationProcessInfo.java index a59e73127..3b8734217 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValidationProcessInfo.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValidationProcessInfo.java @@ -29,7 +29,7 @@ public class ValidationProcessInfo { return issues; } public void addIssue(List issues) { - issues.addAll(issues); + this.issues.addAll(issues); } public boolean hasErrors() { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java index 5fe76d5c5..df425b3fc 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java @@ -97,7 +97,6 @@ import com.google.j2objc.annotations.ReflectionSupport.Level; public class ValueSetValidator extends ValueSetProcessBase { private ValueSet valueset; - private IWorkerContext context; private Map inner = new HashMap<>(); private ValidationOptions options; private ValidationContextCarrier localContext; @@ -215,7 +214,7 @@ public class ValueSetValidator extends ValueSetProcessBase { } } else { c.setUserData("cs", cs); - res = validateCode(path+".coding["+i+"]", c, cs, vcc); + res = validateCode(path+".coding["+i+"]", c, cs, vcc, info); } info.getIssues().addAll(res.getIssues()); i++; @@ -229,7 +228,7 @@ public class ValueSetValidator extends ValueSetProcessBase { for (Coding c : code.getCoding()) { b.append(c.getSystem()+(c.hasVersion() ? "|"+c.getVersion() : "")+"#"+c.getCode()); - Boolean ok = codeInValueSet(c.getSystem(), c.getVersion(), c.getCode(), info); + Boolean ok = codeInValueSet(path, c.getSystem(), c.getVersion(), c.getCode(), info); if (ok == null && result != null && result == false) { result = null; } else if (ok != null && ok) { @@ -338,30 +337,6 @@ public class ValueSetValidator extends ValueSetProcessBase { return versionTest == null && VersionUtilities.versionsMatch(versionTest, versionActual); } - private List makeIssue(IssueSeverity level, IssueType type, String location, String message) { - OperationOutcomeIssueComponent result = new OperationOutcomeIssueComponent(); - switch (level) { - case ERROR: - result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR); - break; - case FATAL: - result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.FATAL); - break; - case INFORMATION: - result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION); - break; - case WARNING: - result.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING); - break; - } - result.setCode(type); - result.addLocation(location); - result.getDetails().setText(message); - ArrayList list = new ArrayList<>(); - list.add(result); - return list; - } - public ValidationResult validateCode(Coding code) throws FHIRException { return validateCode("Coding", code); } @@ -374,7 +349,9 @@ public class ValueSetValidator extends ValueSetProcessBase { boolean inExpansion = false; boolean inInclude = false; List issues = new ArrayList<>(); + ValidationProcessInfo info = new ValidationProcessInfo(issues); VersionInfo vi = new VersionInfo(this); + checkCanonical(issues, path, valueset); String system = code.hasSystem() ? code.getSystem() : getValueSetSystemOrNull(); if (options.getValueSetMode() != ValueSetMode.CHECK_MEMERSHIP_ONLY) { @@ -434,6 +411,8 @@ public class ValueSetValidator extends ValueSetProcessBase { } } } + } else { + checkCanonical(issues, path, cs); } if (cs != null && cs.hasSupplements()) { String msg = context.formatMessage(I18nConstants.CODESYSTEM_CS_NO_SUPPLEMENT, cs.getUrl()); @@ -461,7 +440,8 @@ public class ValueSetValidator extends ValueSetProcessBase { // we can't validate that here. throw new FHIRException("Unable to evaluate based on empty code system"); } - res = validateCode(path, code, cs, null); + res = validateCode(path, code, cs, null, info); + res.setIssues(issues); } else if (cs == null && valueset.hasExpansion() && inExpansion) { // we just take the value set as face value then res = new ValidationResult(system, wv, new ConceptDefinitionComponent().setCode(code.getCode()).setDisplay(code.getDisplay()), code.getDisplay()); @@ -481,12 +461,11 @@ public class ValueSetValidator extends ValueSetProcessBase { } String wv = vi.getVersion(system, code.getVersion()); - ValidationProcessInfo info = new ValidationProcessInfo(issues); // then, if we have a value set, we check it's in the value set if (valueset != null && options.getValueSetMode() != ValueSetMode.NO_MEMBERSHIP_CHECK) { if ((res==null || res.isOk())) { - Boolean ok = codeInValueSet(system, wv, code.getCode(), info); + Boolean ok = codeInValueSet(path, system, wv, code.getCode(), info); if (ok == null || !ok) { if (res == null) { res = new ValidationResult((IssueSeverity) null, null, info.getIssues()); @@ -654,7 +633,7 @@ public class ValueSetValidator extends ValueSetProcessBase { return false; } - private ValidationResult validateCode(String path, Coding code, CodeSystem cs, CodeableConcept vcc) { + private ValidationResult validateCode(String path, Coding code, CodeSystem cs, CodeableConcept vcc, ValidationProcessInfo info) { ConceptDefinitionComponent cc = cs.hasUserData("tx.cs.special") ? ((SpecialCodeSystem) cs.getUserData("tx.cs.special")).findConcept(code) : findCodeInConcept(cs.getConcept(), code.getCode(), allAltCodes); if (cc == null) { if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { @@ -669,6 +648,10 @@ public class ValueSetValidator extends ValueSetProcessBase { if (vcc != null) { vcc.addCoding(vc); } + if (CodeSystemUtilities.isInactive(cs, cc)) { + info.addIssue(makeIssue(IssueSeverity.WARNING, IssueType.EXPIRED, path, context.formatMessage(I18nConstants.INACTIVE_CODE_WARNING, cc.getCode()))); + } + boolean ws = false; if (code.getDisplay() == null) { return new ValidationResult(code.getSystem(), cs.getVersion(), cc, vc.getDisplay()); } @@ -677,14 +660,20 @@ public class ValueSetValidator extends ValueSetProcessBase { b.append("'"+cc.getDisplay()+"'"); if (code.getDisplay().equalsIgnoreCase(cc.getDisplay())) { return new ValidationResult(code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs)); + } else if (Utilities.normalize(code.getDisplay()).equals(Utilities.normalize(cc.getDisplay()))) { + ws = true; } } + for (ConceptDefinitionDesignationComponent ds : cc.getDesignation()) { if (isOkLanguage(ds.getLanguage())) { b.append("'"+ds.getValue()+"'"); if (code.getDisplay().equalsIgnoreCase(ds.getValue())) { return new ValidationResult(code.getSystem(),cs.getVersion(), cc, getPreferredDisplay(cc, cs)); } + if (Utilities.normalize(code.getDisplay()).equalsIgnoreCase(Utilities.normalize(ds.getValue()))) { + ws = true; + } } } // also check to see if the value set has another display @@ -711,7 +700,7 @@ public class ValueSetValidator extends ValueSetProcessBase { String msg = context.formatMessagePlural(options.getLanguages().size(), I18nConstants.NO_VALID_DISPLAY_FOUND, code.getSystem(), code.getCode(), code.getDisplay(), options.langSummary()); return new ValidationResult(IssueSeverity.WARNING, msg, code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs), makeIssue(IssueSeverity.WARNING, IssueType.INVALID, path+".display", msg)); } else { - String msg = context.formatMessagePlural(b.count(), I18nConstants.DISPLAY_NAME_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF, code.getSystem(), code.getCode(), b.toString(), code.getDisplay(), options.langSummary()); + String msg = context.formatMessagePlural(b.count(), ws ? I18nConstants.DISPLAY_NAME_WS_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF : I18nConstants.DISPLAY_NAME_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF, code.getSystem(), code.getCode(), b.toString(), code.getDisplay(), options.langSummary()); return new ValidationResult(dispWarningStatus(), msg, code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs), makeIssue(dispWarning(), IssueType.INVALID, path+".display", msg)); } } @@ -982,14 +971,11 @@ public class ValueSetValidator extends ValueSetProcessBase { return true; } - public Boolean codeInValueSet(String system, String version, String code, ValidationProcessInfo info) throws FHIRException { - return codeInValueSet("code", system, version, code, info); - } - public Boolean codeInValueSet(String path, String system, String version, String code, ValidationProcessInfo info) throws FHIRException { if (valueset == null) { return false; } + checkCanonical(info.getIssues(), path, valueset); Boolean result = false; VersionInfo vi = new VersionInfo(this); @@ -1029,15 +1015,15 @@ public class ValueSetValidator extends ValueSetProcessBase { if (isValueSetUnionImports()) { ok = false; for (UriType uri : vsi.getValueSet()) { - if (inImport(uri.getValue(), system, version, code)) { + if (inImport(path, uri.getValue(), system, version, code, info)) { return true; } } } else { - ok = inImport(vsi.getValueSet().get(0).getValue(), system, version, code); + ok = inImport(path, vsi.getValueSet().get(0).getValue(), system, version, code, info); for (int i = 1; i < vsi.getValueSet().size(); i++) { UriType uri = vsi.getValueSet().get(i); - ok = ok && inImport(uri.getValue(), system, version, code); + ok = ok && inImport(path, uri.getValue(), system, version, code, info); } } } @@ -1097,6 +1083,13 @@ public class ValueSetValidator extends ValueSetProcessBase { return null; } } else { + checkCanonical(info.getIssues(), path, cs); + if (valueset.getCompose().hasInactive() && !valueset.getCompose().getInactive()) { + if (CodeSystemUtilities.isInactive(cs, code)) { + return false; + } + } + if (vsi.hasFilter()) { ok = true; for (ConceptSetFilterComponent f : vsi.getFilter()) { @@ -1231,12 +1224,12 @@ public class ValueSetValidator extends ValueSetProcessBase { return vsc; } - private boolean inImport(String uri, String system, String version, String code) throws FHIRException { + private boolean inImport(String path, String uri, String system, String version, String code, ValidationProcessInfo info) throws FHIRException { ValueSetValidator vs = getVs(uri); if (vs == null) { return false; } else { - Boolean ok = vs.codeInValueSet(system, version, code, null); + Boolean ok = vs.codeInValueSet(path, system, version, code, info); return ok != null && ok; } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java index cce645c47..2d19973a8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java @@ -45,7 +45,7 @@ public class CompareUtilities extends BaseTestingUtilities { String result = compareXml(expected, actual); if (result != null && SHOW_DIFF) { String diff = getDiffTool(); - if (new File(diff).exists() || Utilities.isToken(diff)) { + if (diff != null && new File(diff).exists() || Utilities.isToken(diff)) { Runtime.getRuntime().exec(new String[]{diff, expected, actual}); } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index aa766d10f..64d8d36c3 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -668,7 +668,7 @@ public class FHIRPathEngine { if (sd == null) { throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT, t); } - types = new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()); + types.addType(sd.getUrl()); } else { String ctxt = t.substring(0, t.indexOf('.')); StructureDefinition sd = cu.findType(ctxt); @@ -680,11 +680,10 @@ public class FHIRPathEngine { throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_CONTEXT_ELEMENT, t); } if (ed.fixedType != null) { - types = new TypeDetails(CollectionStatus.SINGLETON, ed.fixedType); + types.addType(ed.fixedType); } else if (ed.getDefinition().getType().isEmpty() || isAbstractType(ed.getDefinition().getType())) { - types = new TypeDetails(CollectionStatus.SINGLETON, sd.getType()+"#"+t); + types.addType(sd.getType()+"#"+t); } else { - types = new TypeDetails(CollectionStatus.SINGLETON); for (TypeRefComponent tt : ed.getDefinition().getType()) { types.addType(tt.getCode()); } @@ -3545,7 +3544,7 @@ public class FHIRPathEngine { return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime); case Resolve : { checkContextReference(focus, "resolve", exp); - return new TypeDetails(CollectionStatus.ORDERED, "DomainResource"); + return new TypeDetails(focus.getCollectionStatus(), "Resource"); } case Extension : { checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String)); 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 c2ecfcf9f..6c5b94e3f 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 @@ -256,4 +256,34 @@ public class XVerExtensionManager { return v.length() == 3 && Character.isDigit(v.charAt(0)) && v.charAt(1) == '.' && Character.isDigit(v.charAt(2)); } + public String getReference(String url) { + String version = getVersion(url); + String base = VersionUtilities.getSpecUrl(version); + if (base == null) { + return null; + } else { + String path = url.substring(url.indexOf("-")+1); + if (!path.contains(".")) { + return null; + } + String type = path.substring(0, path.indexOf(".")); + if (Utilities.existsInList(type, "Annotation", "Attachment", "Identifier", "CodeableConcept", "Coding", "Quantity", "Duration", "Range", "Period", "Ratio", "RatioRange", "SampledData", "Signature", "HumanName", "Address", "ContactPoint", "Timing")) { + return Utilities.pathURL(base, "datatypes-definitions.html#"+path+"|"+VersionUtilities.getNameForVersion(version)+" "+path); + } + if (Utilities.existsInList(type, "Element", "BackboneElement", "BackboneType", "PrimitiveType", "DataType", "Base")) { + return Utilities.pathURL(base, "types-definitions.html#"+path+"|"+VersionUtilities.getNameForVersion(version)+" "+path); + } + if (Utilities.existsInList(type, "UsageContext", "RelatedArtifact", "DataRequirement", "ParameterDefinition", "TriggerDefinition", "Expression", "ContactDetail", "ExtendedContactDetail", "VirtualServiceDetail", "Availability", "MonetaryComponent", "Contributor")) { + return Utilities.pathURL(base, "metadatatypes-definitions.html#"+path+"|"+VersionUtilities.getNameForVersion(version)+" "+path); + } + if (Utilities.existsInList(type, "Reference", "CodeableReference")) { + return Utilities.pathURL(base, "references-definitions.html#"+path+"|"+VersionUtilities.getNameForVersion(version)+" "+path); + } + if (Utilities.existsInList(type, "Meta")) { + return Utilities.pathURL(base, "resource-definitions.html#"+path+"|"+VersionUtilities.getNameForVersion(version)+" "+path); + } + return Utilities.pathURL(base, type.toLowerCase()+"-definitions.html#"+path+"|"+VersionUtilities.getNameForVersion(version)+" "+path); + } + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/TerminologyCacheTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/TerminologyCacheTests.java index 8c4bccb63..96817110c 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/TerminologyCacheTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/context/TerminologyCacheTests.java @@ -149,7 +149,7 @@ public class TerminologyCacheTests implements ResourceLoaderTests { assertCanonicalResourceEquals(terminologyCapabilities, terminologyCacheB.getTerminologyCapabilities()); assertCanonicalResourceEquals(capabilityStatement, terminologyCacheB.getCapabilityStatement()); - assertValidationResultEquals(codingResultA, terminologyCacheB.getValidation( terminologyCacheA.generateValidationToken(CacheTestUtils.validationOptions, coding, valueSet, new Parameters()))); + assertValidationResultEquals(codingResultA, terminologyCacheB.getValidation(terminologyCacheA.generateValidationToken(CacheTestUtils.validationOptions, coding, valueSet, new Parameters()))); assertValidationResultEquals(codeableConceptResultA, terminologyCacheB.getValidation(terminologyCacheA.generateValidationToken(CacheTestUtils.validationOptions, concept, valueSet, new Parameters()))); assertExpansionOutcomeEquals(expansionOutcomeA,terminologyCacheB.getExpansion(terminologyCacheA.generateExpandToken(valueSet, true))); } diff --git a/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/lang.cache b/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/lang.cache index f78b45815..acd76ade6 100644 --- a/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/lang.cache +++ b/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/lang.cache @@ -12,3 +12,42 @@ e: { "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" } ------------------------------------------------------------------------------------- +{"hierarchical" : false, "valueSet" :{ + "resourceType" : "ValueSet", + "compose" : { + "inactive" : true, + "include" : [{ + "system" : "urn:ietf:bcp:47" + }] + } +}}#### +e: { + "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" +} +------------------------------------------------------------------------------------- +{"hierarchical" : false, "valueSet" :{ + "resourceType" : "ValueSet", + "compose" : { + "inactive" : true, + "include" : [{ + "system" : "urn:ietf:bcp:47" + }] + } +}}#### +e: { + "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" +} +------------------------------------------------------------------------------------- +{"hierarchical" : false, "valueSet" :{ + "resourceType" : "ValueSet", + "compose" : { + "inactive" : true, + "include" : [{ + "system" : "urn:ietf:bcp:47" + }] + } +}}#### +e: { + "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/loinc.cache b/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/loinc.cache index 0216bd7fe..0b62b88a1 100644 --- a/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/loinc.cache +++ b/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/loinc.cache @@ -44,3 +44,138 @@ e: { "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" } ------------------------------------------------------------------------------------- +{"hierarchical" : false, "valueSet" :{ + "resourceType" : "ValueSet", + "compose" : { + "inactive" : true, + "include" : [{ + "system" : "http://loinc.org", + "concept" : [{ + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-label", + "valueString" : "A." + }], + "code" : "LA20752-4", + "display" : "Within 24 hours" + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-label", + "valueString" : "B." + }], + "code" : "LA20753-2", + "display" : "After 24 hours but before 3 days" + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-label", + "valueString" : "C." + }], + "code" : "LA20754-0", + "display" : "Three days or later" + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-label", + "valueString" : "D." + }], + "code" : "LA4489-6", + "display" : "Unknown" + }] + }] + } +}}#### +e: { + "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" +} +------------------------------------------------------------------------------------- +{"hierarchical" : false, "valueSet" :{ + "resourceType" : "ValueSet", + "compose" : { + "inactive" : true, + "include" : [{ + "system" : "http://loinc.org", + "concept" : [{ + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-label", + "valueString" : "A." + }], + "code" : "LA20752-4", + "display" : "Within 24 hours" + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-label", + "valueString" : "B." + }], + "code" : "LA20753-2", + "display" : "After 24 hours but before 3 days" + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-label", + "valueString" : "C." + }], + "code" : "LA20754-0", + "display" : "Three days or later" + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-label", + "valueString" : "D." + }], + "code" : "LA4489-6", + "display" : "Unknown" + }] + }] + } +}}#### +e: { + "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" +} +------------------------------------------------------------------------------------- +{"hierarchical" : false, "valueSet" :{ + "resourceType" : "ValueSet", + "compose" : { + "inactive" : true, + "include" : [{ + "system" : "http://loinc.org", + "concept" : [{ + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-label", + "valueString" : "A." + }], + "code" : "LA20752-4", + "display" : "Within 24 hours" + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-label", + "valueString" : "B." + }], + "code" : "LA20753-2", + "display" : "After 24 hours but before 3 days" + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-label", + "valueString" : "C." + }], + "code" : "LA20754-0", + "display" : "Three days or later" + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-label", + "valueString" : "D." + }], + "code" : "LA4489-6", + "display" : "Unknown" + }] + }] + } +}}#### +e: { + "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/mimetypes.cache b/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/mimetypes.cache index 5aa448890..d9840e042 100644 --- a/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/mimetypes.cache +++ b/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/mimetypes.cache @@ -12,3 +12,42 @@ e: { "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" } ------------------------------------------------------------------------------------- +{"hierarchical" : false, "valueSet" :{ + "resourceType" : "ValueSet", + "compose" : { + "inactive" : true, + "include" : [{ + "system" : "urn:ietf:bcp:13" + }] + } +}}#### +e: { + "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" +} +------------------------------------------------------------------------------------- +{"hierarchical" : false, "valueSet" :{ + "resourceType" : "ValueSet", + "compose" : { + "inactive" : true, + "include" : [{ + "system" : "urn:ietf:bcp:13" + }] + } +}}#### +e: { + "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" +} +------------------------------------------------------------------------------------- +{"hierarchical" : false, "valueSet" :{ + "resourceType" : "ValueSet", + "compose" : { + "inactive" : true, + "include" : [{ + "system" : "urn:ietf:bcp:13" + }] + } +}}#### +e: { + "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/ucum.cache b/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/ucum.cache index ee9e71d25..26af816f9 100644 --- a/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/ucum.cache +++ b/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/ucum.cache @@ -96,3 +96,294 @@ e: { "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" } ------------------------------------------------------------------------------------- +{"hierarchical" : false, "valueSet" :{ + "resourceType" : "ValueSet", + "compose" : { + "inactive" : true, + "include" : [{ + "system" : "http://unitsofmeasure.org", + "concept" : [{ + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "second" + }], + "code" : "s", + "display" : "second", + "designation" : [{ + "language" : "zh", + "value" : "秒" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "minute" + }], + "code" : "min", + "display" : "minute", + "designation" : [{ + "language" : "zh", + "value" : "分钟" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "hour" + }], + "code" : "h", + "display" : "hour", + "designation" : [{ + "language" : "zh", + "value" : "小时" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "day" + }], + "code" : "d", + "display" : "day", + "designation" : [{ + "language" : "zh", + "value" : "天" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "week" + }], + "code" : "wk", + "display" : "week", + "designation" : [{ + "language" : "zh", + "value" : "星期" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "month - Normal practice is to use the 'mo' code as a calendar month when calculating the next occurrence." + }], + "code" : "mo", + "display" : "month", + "designation" : [{ + "language" : "zh", + "value" : "月" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "year" + }], + "code" : "a", + "display" : "year", + "designation" : [{ + "language" : "zh", + "value" : "年" + }] + }] + }] + } +}}#### +e: { + "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" +} +------------------------------------------------------------------------------------- +{"hierarchical" : false, "valueSet" :{ + "resourceType" : "ValueSet", + "compose" : { + "inactive" : true, + "include" : [{ + "system" : "http://unitsofmeasure.org", + "concept" : [{ + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "second" + }], + "code" : "s", + "display" : "second", + "designation" : [{ + "language" : "zh", + "value" : "秒" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "minute" + }], + "code" : "min", + "display" : "minute", + "designation" : [{ + "language" : "zh", + "value" : "分钟" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "hour" + }], + "code" : "h", + "display" : "hour", + "designation" : [{ + "language" : "zh", + "value" : "小时" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "day" + }], + "code" : "d", + "display" : "day", + "designation" : [{ + "language" : "zh", + "value" : "天" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "week" + }], + "code" : "wk", + "display" : "week", + "designation" : [{ + "language" : "zh", + "value" : "星期" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "month - Normal practice is to use the 'mo' code as a calendar month when calculating the next occurrence." + }], + "code" : "mo", + "display" : "month", + "designation" : [{ + "language" : "zh", + "value" : "月" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "year" + }], + "code" : "a", + "display" : "year", + "designation" : [{ + "language" : "zh", + "value" : "年" + }] + }] + }] + } +}}#### +e: { + "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" +} +------------------------------------------------------------------------------------- +{"hierarchical" : false, "valueSet" :{ + "resourceType" : "ValueSet", + "compose" : { + "inactive" : true, + "include" : [{ + "system" : "http://unitsofmeasure.org", + "concept" : [{ + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "second" + }], + "code" : "s", + "display" : "second", + "designation" : [{ + "language" : "zh", + "value" : "秒" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "minute" + }], + "code" : "min", + "display" : "minute", + "designation" : [{ + "language" : "zh", + "value" : "分钟" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "hour" + }], + "code" : "h", + "display" : "hour", + "designation" : [{ + "language" : "zh", + "value" : "小时" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "day" + }], + "code" : "d", + "display" : "day", + "designation" : [{ + "language" : "zh", + "value" : "天" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "week" + }], + "code" : "wk", + "display" : "week", + "designation" : [{ + "language" : "zh", + "value" : "星期" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "month - Normal practice is to use the 'mo' code as a calendar month when calculating the next occurrence." + }], + "code" : "mo", + "display" : "month", + "designation" : [{ + "language" : "zh", + "value" : "月" + }] + }, + { + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/valueset-concept-definition", + "valueString" : "year" + }], + "code" : "a", + "display" : "year", + "designation" : [{ + "language" : "zh", + "value" : "年" + }] + }] + }] + } +}}#### +e: { + "error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.report/pom.xml b/org.hl7.fhir.report/pom.xml index d0c646db0..7b44f4c5f 100644 --- a/org.hl7.fhir.report/pom.xml +++ b/org.hl7.fhir.report/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.0.24-SNAPSHOT + 6.0.26-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml index f49299f31..475ec5a2a 100644 --- a/org.hl7.fhir.utilities/pom.xml +++ b/org.hl7.fhir.utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.0.24-SNAPSHOT + 6.0.26-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/CSFile.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/CSFile.java index b071162e8..975328f7f 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/CSFile.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/CSFile.java @@ -65,8 +65,9 @@ public class CSFile extends File { //attempt to open a file triggers a directory listing if (exists()) { - if(!this.getCanonicalFile().getName().equals(this.getName())) + if(!this.getCanonicalFile().getName().equals(this.getName())) { throw new Error("Case mismatch of file "+ pathname+": found "+this.getCanonicalFile().getName()); + } } } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/StandardsStatus.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/StandardsStatus.java index e39aa5caf..059fa0d0c 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/StandardsStatus.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/StandardsStatus.java @@ -35,7 +35,7 @@ import org.hl7.fhir.exceptions.FHIRException; public enum StandardsStatus { - EXTERNAL, INFORMATIVE, DRAFT, TRIAL_USE, DEPRECATED, NORMATIVE; + EXTERNAL, INFORMATIVE, DRAFT, TRIAL_USE, DEPRECATED, NORMATIVE, WITHDRAWN; public String toDisplay() { switch (this) { @@ -51,6 +51,8 @@ public enum StandardsStatus { return "External"; case DEPRECATED: return "Deprecated"; + case WITHDRAWN: + return "Withdrawn"; } return "?"; } @@ -66,9 +68,11 @@ public enum StandardsStatus { case INFORMATIVE: return "informative"; case DEPRECATED: - return "deprecated"; + return "deprecated"; case EXTERNAL: return "external"; + case WITHDRAWN: + return "Withdrawn"; } return "?"; } @@ -93,6 +97,8 @@ public enum StandardsStatus { return EXTERNAL; if (value.equalsIgnoreCase("DEPRECATED")) return DEPRECATED; + if (value.equalsIgnoreCase("WITHDRAWN")) + return WITHDRAWN; throw new FHIRException("Incorrect Standards Status '"+value+"'"); } @@ -110,6 +116,8 @@ public enum StandardsStatus { return "XD"; case EXTERNAL: return "X"; + case WITHDRAWN: + return "W"; } return "?"; } @@ -125,6 +133,7 @@ public enum StandardsStatus { case INFORMATIVE: return "#ffffe6"; case DEPRECATED: + case WITHDRAWN: return "#ffcccc"; case EXTERNAL: return "#e6ffff"; @@ -143,6 +152,7 @@ public enum StandardsStatus { case INFORMATIVE: return "#ffffec"; case DEPRECATED: + case WITHDRAWN: return "#ffcccc"; case EXTERNAL: return "#ecffff"; @@ -159,6 +169,8 @@ public enum StandardsStatus { return (tgtSS == NORMATIVE || tgtSS == EXTERNAL ); if (this == DEPRECATED) return (tgtSS == DEPRECATED ); + if (this == WITHDRAWN) + return (tgtSS == WITHDRAWN ); return false; } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java index 828327aed..13d18d707 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java @@ -307,7 +307,18 @@ public class Utilities { CSFile src = new CSFile(sourceFolder); if (!src.exists()) throw new FHIRException("Folder " + sourceFolder + " not found"); - createDirectory(destFolder); + File dst = new File(destFolder); + if(!dst.getCanonicalFile().getName().equals(dst.getName())) { + File tmp = new File(destFolder+System.currentTimeMillis()); + if (!dst.renameTo(tmp)) { + throw new IOException("fixing case from "+dst.getCanonicalFile().getName()+" to "+tmp.getName()+" failed"); + } + if (!tmp.renameTo(dst)) { + throw new IOException("fixing case from "+tmp.getCanonicalFile().getName()+" to "+dst.getName()+" failed"); + } + } else if (!dst.exists()) { + createDirectory(destFolder); + } String[] files = src.list(); for (String f : files) { @@ -318,7 +329,7 @@ public class Utilities { } else { if (notifier != null) notifier.copyFile(sourceFolder + File.separator + f, destFolder + File.separator + f); - copyFile(new CSFile(sourceFolder + File.separator + f), new CSFile(destFolder + File.separator + f)); + copyFile(new CSFile(sourceFolder + File.separator + f), new /*CS*/File(destFolder + File.separator + f)); // case doesn't have to match on the target } } } @@ -358,6 +369,10 @@ public class Utilities { createDirectory(destFile.getParent()); } destFile.createNewFile(); + } else if (!destFile.getCanonicalFile().getName().equals(destFile.getName())) { + // case mismatch + destFile.delete(); + destFile.createNewFile(); } FileInputStream source = null; @@ -2112,4 +2127,8 @@ public class Utilities { return url.contains("/") ? url.substring(url.lastIndexOf("/")+1) : url; } + public static String escapeSql(String s) { + return s.replace("'", "''"); + } + } 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 e98520386..c4494e86a 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 @@ -81,6 +81,7 @@ public class I18nConstants { public static final String DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES = "Discriminator__is_based_on_type_but_slice__in__has_no_types"; public static final String DISCRIMINATOR_BAD_PATH = "DISCRIMINATOR_BAD_PATH"; public static final String DISPLAY_NAME_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF = "Display_Name_for__should_be_one_of__instead_of"; + public static final String DISPLAY_NAME_WS_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF = "Display_Name_WS_for__should_be_one_of__instead_of"; public static final String DOCUMENT = "documentmsg"; public static final String DOCUMENT_DATE_REQUIRED = "Bundle_Document_Date_Missing"; public static final String DOCUMENT_DATE_REQUIRED_HTML = "Bundle_Document_Date_Missing_html"; @@ -949,9 +950,12 @@ public class I18nConstants { public static final String ED_INVARIANT_KEY_ALREADY_USED = "ED_INVARIANT_KEY_ALREADY_USED"; public static final String FHIRPATH_OFTYPE_IMPOSSIBLE = "FHIRPATH_OFTYPE_IMPOSSIBLE"; public static final String ED_SEARCH_EXPRESSION_ERROR = "ED_SEARCH_EXPRESSION_ERROR"; - - - + public static final String SD_EXTENSION_URL_MISMATCH = "SD_EXTENSION_URL_MISMATCH"; + public static final String MSG_DEPRECATED = "MSG_DEPRECATED"; + public static final String MSG_WITHDRAWN = "MSG_WITHDRAWN"; + public static final String MSG_RETIRED = "MSG_RETIRED"; + public static final String INACTIVE_CODE_WARNING = "INACTIVE_CODE_WARNING"; + public static final String SD_EXTENSION_URL_MISSING = "SD_EXTENSION_URL_MISSING"; } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/NpmPackage.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/NpmPackage.java index a9098c50a..df3f36382 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/NpmPackage.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/NpmPackage.java @@ -297,6 +297,7 @@ public class NpmPackage { private Map userData; private boolean minimalMemory; private int size; + private boolean warned = false; /** * Constructor @@ -1266,6 +1267,11 @@ public class NpmPackage { return Utilities.existsInList(npm.asString("type"), "fhir.core", "Core"); } + public boolean isCoreExamples() { + return name().startsWith("hl7.fhir.r") && name().endsWith(".examples"); + } + + public boolean isTx() { return npm.asString("name").startsWith("hl7.terminology"); } @@ -1369,5 +1375,14 @@ public class NpmPackage { public void setSize(int size) { this.size = size; } + + public boolean isWarned() { + return warned; + } + + public void setWarned(boolean warned) { + this.warned = warned; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationMessage.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationMessage.java index b2ec9864b..644fb6cc7 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationMessage.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationMessage.java @@ -859,6 +859,50 @@ public class ValidationMessage implements Comparator, Compara this.ignorableError = ignorableError; return this; } + + public boolean matches(ValidationMessage other) { + if (location == null) { + if (other.location != null) { + return false; + } + } else { + String l1 = preprocessLocation(location); + String l2 = preprocessLocation(other.location); + if (!l1.equals(l2)) { + return false; + } + } + if (message == null) { + if (other.message != null) { + return false; + } + } else if (!message.equals(other.message)) { + return false; + } + if (messageId == null) { + if (other.messageId != null) { + return false; + } + } else if (!messageId.equals(other.messageId)) { + return false; + } + if (type != other.type) { + return false; + } + if (level != other.level) { + return false; + } + return true; + } + + private String preprocessLocation(String loc) { + // some locations are prefixes with a location but they're not different since the location is fixed where .match is called from + if (loc.contains(": ")) { + return loc.substring(loc.indexOf(": ")+2); + } + return loc; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 9784c3d44..5de2104e7 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -171,7 +171,7 @@ Terminology_TX_NoValid_13 = The Coding provided ({2}) is not in the value set {0 Terminology_TX_NoValid_14 = The Coding provided ({2}) is not in the value set {0}, and a code is recommended to come from this value set. {1} Terminology_TX_NoValid_15 = The value provided (''{0}'') could not be validated in the absence of a terminology server Terminology_TX_NoValid_15A = The value provided (''{0}'') could not be validated because the code system {1} is not known -Terminology_TX_NoValid_16 = The value provided (''{0}'') is not in the value set {1}, and a code is required from this value set){2} +Terminology_TX_NoValid_16 = The value provided (''{0}'') is not in the value set {1}, and a code is required from this value set {2} Terminology_TX_NoValid_17 = The value provided (''{0}'') is not in the value set {1}, and a code should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable) {2} Terminology_TX_NoValid_18 = The value provided (''{0}'') is not in the value set {1}, and a code is recommended to come from this value set){2} Terminology_TX_NoValid_2_CC = None of the codings provided are in the value set {0}, and a coding should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable) (codes = {1}) @@ -468,6 +468,8 @@ Error_parsing_ = Error parsing {0}:{1} Unable_to_connect_to_terminology_server_Use_parameter_tx_na_tun_run_without_using_terminology_services_to_validate_LOINC_SNOMED_ICDX_etc_Error__ = Unable to connect to terminology server. Use parameter ''-tx n/a'' to run without using terminology services to validate LOINC, SNOMED, ICD-X etc. Error = {0} Display_Name_for__should_be_one_of__instead_of_one = Wrong Display Name ''{4}'' for {1}#{2} - should be {3} (for the language(s) ''{5}'') Display_Name_for__should_be_one_of__instead_of_other = Wrong Display Name ''{4}'' for {1}#{2} - should be one of {0} choices: {3} (for the language(s) ''{5}'') +Display_Name_WS_for__should_be_one_of__instead_of_one = Wrong whitespace in Display Name ''{4}'' for {1}#{2} - should be {3} (for the language(s) ''{5}'') +Display_Name_WS_for__should_be_one_of__instead_of_other = Wrong whitespace in Display Name ''{4}'' for {1}#{2} - should be one of {0} choices: {3} (for the language(s) ''{5}'') Unknown_Code__in_ = Unknown Code ''{0}'' in the system ''{1}'' UNKNOWN_CODE__IN_FRAGMENT = Unknown Code ''{0}'' in the system ''{1}'' - note that the code system is labeled as a fragment, so the code may be valid in some other fragment Code_found_in_expansion_however_ = Code found in expansion, however: {0} @@ -1004,4 +1006,11 @@ ED_INVARIANT_DIFF_NO_SOURCE = The invariant {0} defined in the differential must FHIRPATH_COLLECTION_STATUS_OPERATION_LEFT = The left side is inherently a collection, and so the expression ''{0}'' may fail or return false if there is more than one item in the content being evaluated FHIRPATH_COLLECTION_STATUS_OPERATION_RIGHT = The right side is inherently a collection, and so this expression ''{0}'' may fail or return false if there is more than one item in the content being evaluated FHIRPATH_OFTYPE_IMPOSSIBLE = The type specified in ofType is {1} which is not a possible candidate for the existing types ({0}) in the expression {2}. Check the paths and types to be sure this is what is intended -ED_SEARCH_EXPRESSION_ERROR = Error in search expression ''{0}'': {1} \ No newline at end of file +ED_SEARCH_EXPRESSION_ERROR = Error in search expression ''{0}'': {1} +SD_EXTENSION_URL_MISMATCH = The fixed value for the extension URL is {1} which doesn''t match the canonical URL {0} +SD_EXTENSION_URL_MISSING = The value of Extension.url is not fixed to the extension URL {0} +MSG_DEPRECATED = Reference to deprecated item {0} +MSG_WITHDRAWN = Reference to withdrawn item {0} +MSG_RETIRED = Reference to retired item {0} +INACTIVE_CODE_WARNING = The code ''{0}'' is valid but is not active +SD_ED_TYPE_PROFILE_WRONG_TYPE = The type {0} is not in the list of allowed types {1} in the profile {2} diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages_es.properties b/org.hl7.fhir.utilities/src/main/resources/Messages_es.properties index b209a888d..74f331de3 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages_es.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages_es.properties @@ -831,4 +831,7 @@ PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE_many = PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE_other = UNICODE_XML_BAD_CHARS_one = UNICODE_XML_BAD_CHARS_many = -UNICODE_XML_BAD_CHARS_other = \ No newline at end of file +UNICODE_XML_BAD_CHARS_other = +Display_Name_WS_for__should_be_one_of__instead_of_one = +Display_Name_WS_for__should_be_one_of__instead_of_many = +Display_Name_WS_for__should_be_one_of__instead_of_other = diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/UtilitiesTest.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/UtilitiesTest.java index da5f1f3a3..fd00f3449 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/UtilitiesTest.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/UtilitiesTest.java @@ -422,5 +422,46 @@ class UtilitiesTest { Assertions.assertFalse("\u0009\n\u000B\u000C\r\u0020\u0085\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000".matches(".+")); Assertions.assertFalse("\u0009\n\u000B\u000C\r\u0020\u0085\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000".matches("^.+$")); } - + + @Test + @DisplayName("directory copy case tests") + void testFDirectoryCopy() throws IOException { + String src = Utilities.path("[tmp]", "test", "copy-source"); + String dst = Utilities.path("[tmp]", "test", "copy-dest"); + makeDir (src); + makeFile(Utilities.path(src, "Test.txt"), "source1"); + makeDir (Utilities.path(src, "SUB")); + makeFile(Utilities.path(src, "SUB", "TEST.txt"), "source2"); + + makeDir (dst); + makeFile(Utilities.path(dst, "test.txt"), "dest1"); + makeDir (Utilities.path(dst, "sub")); + makeFile(Utilities.path(dst, "sub", "test.txt"), "dest2"); + + Utilities.copyDirectory(src, dst, null); + + checkDir (dst); + checkFile(Utilities.path(dst, "Test.txt"), "source1"); + checkDir (Utilities.path(dst, "SUB")); + checkFile(Utilities.path(dst, "SUB", "TEST.txt"), "source2"); + } + + private void checkFile(String path, String content) throws IOException { + Assertions.assertTrue(new CSFile(path).exists()); + Assertions.assertEquals(content, TextFile.fileToString(path)); + } + + private void checkDir(String path) throws IOException { + Assertions.assertTrue(new CSFile(path).exists()); + } + + private void makeFile(String path, String content) throws IOException { + TextFile.stringToFile(content, path); + } + + private void makeDir(String path) throws IOException { + Utilities.createDirectory(path); + Utilities.clearDirectory(path); + } } + diff --git a/org.hl7.fhir.validation.cli/pom.xml b/org.hl7.fhir.validation.cli/pom.xml index 6490071ec..034a2209a 100644 --- a/org.hl7.fhir.validation.cli/pom.xml +++ b/org.hl7.fhir.validation.cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.0.24-SNAPSHOT + 6.0.26-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.validation/pom.xml b/org.hl7.fhir.validation/pom.xml index ce37d49e0..f1d784b1a 100644 --- a/org.hl7.fhir.validation/pom.xml +++ b/org.hl7.fhir.validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 6.0.24-SNAPSHOT + 6.0.26-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java index 56f980694..ef39e2750 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java @@ -520,12 +520,14 @@ public class IgLoader implements IValidationEngineLoader { } } - if (loadInContext) { -// getContext().getLoadedPackages().add(pi.name() + "#" + pi.version()); - getContext().loadFromPackage(pi, ValidatorUtils.loaderForVersion(pi.fhirVersion())); - } - for (String s : pi.listResources("CodeSystem", "ConceptMap", "ImplementationGuide", "CapabilityStatement", "SearchParameter", "Conformance", "StructureMap", "ValueSet", "StructureDefinition")) { - res.put(s, TextFile.streamToBytes(pi.load("package", s))); + if (!pi.isCoreExamples()) { + if (loadInContext) { + // getContext().getLoadedPackages().add(pi.name() + "#" + pi.version()); + getContext().loadFromPackage(pi, ValidatorUtils.loaderForVersion(pi.fhirVersion())); + } + for (String s : pi.listResources("CodeSystem", "ConceptMap", "ImplementationGuide", "CapabilityStatement", "SearchParameter", "Conformance", "StructureMap", "ValueSet", "StructureDefinition")) { + res.put(s, TextFile.streamToBytes(pi.load("package", s))); + } } String ini = "[FHIR]\r\nversion=" + pi.fhirVersion() + "\r\n"; res.put("version.info", ini.getBytes()); 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 e65c83ba2..fa6ada3b0 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 @@ -2024,8 +2024,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return true; } plist.add(p); - } + Collections.sort(plist); // logical paths are a set, but we want a predictable order for error messages for (StructureDefinitionContextComponent ctxt : fixContexts(extUrl, definition.getContext())) { if (ok) { @@ -2038,36 +2038,45 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (en.contains("#")) { pu = en.substring(0, en.indexOf("#")); en = en.substring(en.indexOf("#")+1); + } else { + //pu = en; } if (Utilities.existsInList(en, "Element", "Any")) { ok = true; } else if (en.equals("Resource") && container.isResource()) { ok = true; + } else if (en.equals("CanonicalResource") && VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(stack.getLiteralPath())) { + ok = true; + } else if (plist.contains(en) && pu == null) { + ok = true; } - if (checkConformsToProfile(appContext, errors, resource, container, stack, extUrl, ctxt.getExpression(), pu)) { - for (String p : plist) { - if (ok) { - break; - } - if (p.equals(en)) { - ok = true; - } else { - String pn = p; - String pt = ""; - if (p.contains(".")) { - pn = p.substring(0, p.indexOf(".")); - pt = p.substring(p.indexOf(".")); + + if (!ok) { + if (checkConformsToProfile(appContext, errors, resource, container, stack, extUrl, ctxt.getExpression(), pu)) { + for (String p : plist) { + if (ok) { + break; } - StructureDefinition sd = context.fetchTypeDefinition(pn); - while (sd != null) { - if ((sd.getType() + pt).equals(en)) { - ok = true; - break; + if (p.equals(en)) { + ok = true; + } else { + String pn = p; + String pt = ""; + if (p.contains(".")) { + pn = p.substring(0, p.indexOf(".")); + pt = p.substring(p.indexOf(".")); } - if (sd.getBaseDefinition() != null) { - sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); - } else { - sd = null; + StructureDefinition sd = context.fetchTypeDefinition(pn); + while (sd != null) { + if ((sd.getType() + pt).equals(en)) { + ok = true; + break; + } + if (sd.getBaseDefinition() != null) { + sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); + } else { + sd = null; + } } } } @@ -5960,7 +5969,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (ed.hasSlicing()) { if (slicer != null && slicer.getPath().equals(ed.getPath())) { String errorContext = "profile " + profile.getVersionedUrl(); - if (!resource.getChildValue(ID).isEmpty()) { + if (resource.hasChild(ID) && !resource.getChildValue(ID).isEmpty()) { errorContext += "; instance " + resource.getChildValue("id"); } throw new DefinitionException(context.formatMessage(I18nConstants.SLICE_ENCOUNTERED_MIDWAY_THROUGH_SET_PATH___ID___, slicer.getPath(), slicer.getId(), errorContext)); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java index 0e20e2636..6ed6958fb 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java @@ -78,6 +78,8 @@ public class StructureDefinitionValidator extends BaseValidator { StructureDefinition sd = null; String typeName = null; try { + String url = src.getNamedChildValue("url"); + sd = loadAsSD(src); checkExtensionContext(errors, src, stack); @@ -101,8 +103,8 @@ public class StructureDefinitionValidator extends BaseValidator { for (ValidationMessage msg : msgs) { // we need to set the location for the context String loc = msg.getLocation(); - if (loc.contains("#")) { - msg.setLocation(stack.getLiteralPath()+".differential.element.where(path = '"+loc.substring(loc.indexOf("#")+1)+"')"); + if (loc.startsWith("StructureDefinition.")) { + msg.setLocation(stack.getLiteralPath()+loc.substring(loc.indexOf("."))); } else { msg.setLocation(stack.getLiteralPath()); } @@ -160,6 +162,20 @@ public class StructureDefinitionValidator extends BaseValidator { } c++; } + + // if this is defining an extension, make sure that the extension fixed value matches the URL + String type = src.getNamedChildValue("type"); + if ("Extension".equals(type)) { + String baseD = src.getNamedChildValue("baseDefinition"); + if ("http://hl7.org/fhir/StructureDefinition/Extension".equals(baseD) && url != null) { + String fixedUrl = getFixedValue(src); + if (rule(errors, "2023-08-05", IssueType.INVALID, stack.getLiteralPath(), fixedUrl != null, I18nConstants.SD_EXTENSION_URL_MISSING, url)) { + ok = rule(errors, "2023-08-05", IssueType.INVALID, stack.getLiteralPath(), url.equals(fixedUrl), I18nConstants.SD_EXTENSION_URL_MISMATCH, url, fixedUrl) && ok; + } else { + ok = false; + } + } + } } catch (Exception e) { rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage()); ok = false; @@ -168,6 +184,19 @@ public class StructureDefinitionValidator extends BaseValidator { } + private String getFixedValue(Element src) { + Element diff = src.getNamedChild("differential"); + if (diff != null) { + for (Element ed : diff.getChildrenByName("element")) { + String path = ed.getNamedChildValue("path"); + if ("Extension.url".equals(path)) { + return ed.getNamedChildValue("fixed"); + } + } + } + return null; + } + private boolean validateInheritsObligationProfile(List errors, Element extension, NodeStack stack, Element src) { String tgt = extension.getNamedChildValue("value"); if (rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), tgt != null, diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java index f2663cf5d..86ebb6b4f 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java @@ -71,7 +71,7 @@ public class ValueSetValidator extends BaseValidator { List composes = vs.getChildrenByName("compose"); int cc = 0; for (Element compose : composes) { - ok = validateValueSetCompose(errors, compose, stack.push(compose, cc, null, null), vs.getNamedChildValue("url"), "retired".equals(vs.getNamedChildValue("url"))) & ok; + ok = validateValueSetCompose(errors, compose, stack.push(compose, composes.size() > 1 ? cc : -1, null, null), vs.getNamedChildValue("url"), "retired".equals(vs.getNamedChildValue("url"))) & ok; cc++; } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/NodeStack.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/NodeStack.java index e3b192153..32df6e98d 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/NodeStack.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/NodeStack.java @@ -2,8 +2,10 @@ package org.hl7.fhir.validation.instance.utils; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.elementmodel.Element; @@ -19,7 +21,7 @@ public class NodeStack { private Element element; private ElementDefinition extension; private String literalPath; // xpath format - private List logicalPaths; // dotted format, various entry points + private Set logicalPaths; // dotted format, various entry points private NodeStack parent; private ElementDefinition type; private String workingLang; @@ -38,7 +40,7 @@ public class NodeStack { literalPath = (initialPath == null ? "" : initialPath+".") + element.getPath(); workingLang = validationLanguage; if (!element.getName().equals(element.fhirType())) { - logicalPaths = new ArrayList<>(); + logicalPaths = new HashSet<>(); logicalPaths.add(element.fhirType()); } } @@ -79,8 +81,8 @@ public class NodeStack { return literalPath == null ? "" : literalPath; } - public List getLogicalPaths() { - return logicalPaths == null ? new ArrayList() : logicalPaths; + public Set getLogicalPaths() { + return logicalPaths == null ? new HashSet() : logicalPaths; } private ElementDefinition getType() { @@ -96,6 +98,9 @@ public class NodeStack { } private NodeStack pushInternal(Element element, int count, ElementDefinition definition, ElementDefinition type, String sep) { + if (definition == null & element.getProperty() != null) { + definition = element.getProperty().getDefinition(); + } NodeStack res = new NodeStack(context); res.ids = ids; res.parent = this; @@ -121,7 +126,7 @@ public class NodeStack { res.literalPath = res.literalPath.substring(0, res.literalPath.lastIndexOf(".")) + "." + en;; } } - res.logicalPaths = new ArrayList(); + res.logicalPaths = new HashSet(); if (type != null) { // type will be null if we on a stitching point of a contained resource, or if.... res.type = type; @@ -131,23 +136,48 @@ public class NodeStack { tn = element.fhirType(); } for (String lp : getLogicalPaths()) { - res.logicalPaths.add(lp + "." + t); - if (t.endsWith("[x]")) { - res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + ".ofType("+type.getPath()+")"); - res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + type.getPath()); + if (isRealPath(lp, t)) { + res.logicalPaths.add(lp + "." + t); + if (t.endsWith("[x]")) { + res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + ".ofType("+type.getPath()+")"); + res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + type.getPath()); + } } } res.logicalPaths.add(tn); } else if (definition != null) { for (String lp : getLogicalPaths()) { - res.logicalPaths.add(lp + "." + element.getName()); + if (isRealPath(lp, element.getName())) { + res.logicalPaths.add(lp + "." + element.getName()); + } } - res.logicalPaths.add(definition.typeSummary()); - } else + if (definition.hasContentReference()) { + res.logicalPaths.add(definition.getContentReference().substring(definition.getContentReference().indexOf("#")+1)); + } else { + res.logicalPaths.addAll(definition.typeList()); + } + } else { res.logicalPaths.addAll(getLogicalPaths()); + } return res; } + private boolean isRealPath(String lp, String t) { + if (Utilities.existsInList(lp, "Element")) { + return Utilities.existsInList(t, "id", "extension"); + } + if (Utilities.existsInList(lp, "BackboneElement", "BackboneType")) { + return Utilities.existsInList(t, "modifierExtension"); + } + if (Utilities.existsInList(lp, "Resource")) { + return Utilities.existsInList(t, "id", "meta", "implicitRules", "language"); + } + if (Utilities.existsInList(lp, "DomainResource")) { + return Utilities.existsInList(t, "text", "contained", "extension", "modifierExtension"); + } + return true; + } + private void setType(ElementDefinition type) { this.type = type; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTesterSorters.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTesterSorters.java index e896a4523..30e42ce36 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTesterSorters.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/special/TxTesterSorters.java @@ -3,7 +3,11 @@ package org.hl7.fhir.validation.special; import java.util.Collections; import java.util.Comparator; +import org.hl7.fhir.ParametersParameter; +import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.OperationOutcome; +import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent; import org.hl7.fhir.r5.model.Parameters; import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.r5.model.ValueSet; @@ -16,8 +20,14 @@ import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; public class TxTesterSorters { + public static void sortParameters(Parameters po) { Collections.sort(po.getParameter(), new TxTesterSorters.ParameterSorter()); + for (ParametersParameterComponent p : po.getParameter()) { + if (p.getResource() != null && p.getResource() instanceof OperationOutcome) { + Collections.sort(((OperationOutcome) p.getResource()).getIssue(), new TxTesterSorters.OperationIssueSorter()); + } + } } @@ -43,7 +53,24 @@ public class TxTesterSorters { } - + + public static class OperationIssueSorter implements Comparator { + + @Override + public int compare(OperationOutcomeIssueComponent o1, OperationOutcomeIssueComponent o2) { + if (o1.hasSeverity() && o2.hasSeverity() && o1.getSeverity() != o2.getSeverity()) { + return o1.getSeverity().compareTo(o2.getSeverity()); + } + if (o1.hasCode() && o2.hasCode() && o1.getCode() != o2.getCode()) { + return o1.getCode().compareTo(o2.getCode()); + } + if (o1.getDetails().hasText() && o2.getDetails().hasText() && !o1.getDetails().getText().equals(o2.getDetails().getText())) { + return o1.getDetails().getText().compareTo(o2.getDetails().getText()); + } + return 0; + } + } + public static class DesignationSorter implements Comparator { @Override diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java index 98af4d75f..2c3f2d201 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java @@ -107,7 +107,7 @@ public class SnapShotGenerationXTests { sort = "true".equals(test.getAttribute("sort")); fail = "true".equals(test.getAttribute("fail")); newSliceProcessing = !"false".equals(test.getAttribute("new-slice-processing")); - debug = false; // "true".equals(test.getAttribute("debug")); + debug = "true".equals(test.getAttribute("debug")); id = test.getAttribute("id"); include = test.getAttribute("include"); @@ -510,7 +510,7 @@ public class SnapShotGenerationXTests { pu.setAllowUnknownProfile(AllowUnknownProfile.ALL_TYPES); if (test.isSort()) { List errors = new ArrayList(); - int lastCount = output.getDifferential().getElement().size(); +// int lastCount = output.getDifferential().getElement().size(); pu.sortDifferential(base, output, test.getSource().getName(), errors, false); if (errors.size() > 0) throw new FHIRException("Sort failed: " + errors.toString()); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/ExternalTerminologyServiceTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/ExternalTerminologyServiceTests.java index 52795801f..99ba5e71c 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/ExternalTerminologyServiceTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/terminology/tests/ExternalTerminologyServiceTests.java @@ -46,9 +46,9 @@ public class ExternalTerminologyServiceTests implements ITxTesterLoader { private JsonObject test; } - private static final String SERVER = FhirSettings.getTxFhirDevelopment(); -// private static final String SERVER = FhirSettings.getTxFhirLocal(); -// private static final String SERVER = "https://r4.ontoserver.csiro.au/fhir"; + private static final String SERVER = FhirSettings.getTxFhirDevelopment(); +// private static final String SERVER = FhirSettings.getTxFhirLocal(); +// private static final String SERVER = "https://r4.ontoserver.csiro.au/fhir"; @Parameters(name = "{index}: id {0}") diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java index fa6391a4b..b0e401301 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java @@ -563,7 +563,12 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe JsonObject oj = JsonTrackingParser.parse(json, null); java.add("outcome", oj); } - JsonTrackingParser.write(manifest, new File(Utilities.path("[tmp]", "validator", "manifest.new.json"))); + + File newManifestFile = new File(Utilities.path("[tmp]", "validator", "manifest.new.json")); + if (!newManifestFile.getParentFile().exists()) { + newManifestFile.getParentFile().mkdir(); + } + JsonTrackingParser.write(manifest, newManifestFile); } Assertions.fail("\r\n"+String.join("\r\n", fails)); } diff --git a/owasp-suppression-file.xml b/owasp-suppression-file.xml new file mode 100644 index 000000000..8f75b8e2c --- /dev/null +++ b/owasp-suppression-file.xml @@ -0,0 +1,17 @@ + + + + + ^pkg:maven/org\.hl7\.fhir\.testcases/fhir\-test\-cases@.*$ + CVE-2019-12741 + + + + ^pkg:maven/org\.hl7\.fhir\.testcases/fhir\-test\-cases@.*$ + CVE-2021-32053 + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index dfaaaf222..254bb68b6 100644 --- a/pom.xml +++ b/pom.xml @@ -14,13 +14,13 @@ HAPI FHIR --> org.hl7.fhir.core - 6.0.24-SNAPSHOT + 6.0.26-SNAPSHOT pom 32.0.1-jre 6.4.1 - 1.3.22 + 1.3.25 2.15.2 5.9.2 1.8.2 @@ -705,6 +705,11 @@ org.owasp dependency-check-maven + + + owasp-suppression-file.xml + +