diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/TerminologyCache.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/TerminologyCache.java index acd05391e..ae66e4b1d 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/TerminologyCache.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/context/TerminologyCache.java @@ -1,33 +1,33 @@ package org.hl7.fhir.r4.context; -/* - Copyright (c) 2011+, HL7, Inc. - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - */ +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + */ @@ -84,16 +84,88 @@ public class TerminologyCache { private static final String ENTRY_MARKER = "-------------------------------------------------------------------------------------"; private static final String BREAK = "####"; + private SystemNameKeyGenerator systemNameKeyGenerator = new SystemNameKeyGenerator(); + + protected SystemNameKeyGenerator getSystemNameKeyGenerator() { + return systemNameKeyGenerator; + } + + public class SystemNameKeyGenerator { + public static final String SNOMED_SCT_CODESYSTEM_URL = "http://snomed.info/sct"; + public static final String RXNORM_CODESYSTEM_URL = "http://www.nlm.nih.gov/research/umls/rxnorm"; + public static final String LOINC_CODESYSTEM_URL = "http://loinc.org"; + public static final String UCUM_CODESYSTEM_URL = "http://unitsofmeasure.org"; + + public static final String HL7_TERMINOLOGY_CODESYSTEM_BASE_URL = "http://terminology.hl7.org/CodeSystem/"; + public static final String HL7_SID_CODESYSTEM_BASE_URL = "http://hl7.org/fhir/sid/"; + public static final String HL7_FHIR_CODESYSTEM_BASE_URL = "http://hl7.org/fhir/"; + + public static final String ISO_CODESYSTEM_URN = "urn:iso:std:iso:"; + public static final String LANG_CODESYSTEM_URN = "urn:ietf:bcp:47"; + public static final String MIMETYPES_CODESYSTEM_URN = "urn:ietf:bcp:13"; + + public static final String _11073_CODESYSTEM_URN = "urn:iso:std:iso:11073:10101"; + public static final String DICOM_CODESYSTEM_URL = "http://dicom.nema.org/resources/ontology/DCM"; + + public String getNameForSystem(String system) { + final int lastPipe = system.lastIndexOf('|'); + final String systemBaseName = lastPipe == -1 ? system : system.substring(0,lastPipe); + final String systemVersion = lastPipe == -1 ? null : system.substring(lastPipe + 1); + + if (systemBaseName.equals(SNOMED_SCT_CODESYSTEM_URL)) + return getVersionedSystem("snomed", systemVersion); + if (systemBaseName.equals(RXNORM_CODESYSTEM_URL)) + return getVersionedSystem("rxnorm", systemVersion); + if (systemBaseName.equals(LOINC_CODESYSTEM_URL)) + return getVersionedSystem("loinc", systemVersion); + if (systemBaseName.equals(UCUM_CODESYSTEM_URL)) + return getVersionedSystem("ucum", systemVersion); + if (systemBaseName.startsWith(HL7_SID_CODESYSTEM_BASE_URL)) + return getVersionedSystem(normalizeBaseURL(HL7_SID_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); + if (systemBaseName.equals(_11073_CODESYSTEM_URN)) + return getVersionedSystem("11073", systemVersion); + if (systemBaseName.startsWith(ISO_CODESYSTEM_URN)) + return getVersionedSystem("iso"+systemBaseName.substring(ISO_CODESYSTEM_URN.length()).replace(":", ""), systemVersion); + if (systemBaseName.startsWith(HL7_TERMINOLOGY_CODESYSTEM_BASE_URL)) + return getVersionedSystem(normalizeBaseURL(HL7_TERMINOLOGY_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); + if (systemBaseName.startsWith(HL7_FHIR_CODESYSTEM_BASE_URL)) + return getVersionedSystem(normalizeBaseURL(HL7_FHIR_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); + if (systemBaseName.equals(LANG_CODESYSTEM_URN)) + return getVersionedSystem("lang", systemVersion); + if (systemBaseName.equals(MIMETYPES_CODESYSTEM_URN)) + return getVersionedSystem("mimetypes", systemVersion); + if (systemBaseName.equals(DICOM_CODESYSTEM_URL)) + return getVersionedSystem("dicom", systemVersion); + return getVersionedSystem(systemBaseName.replace("/", "_").replace(":", "_").replace("?", "X").replace("#", "X"), systemVersion); + } + + public String normalizeBaseURL(String baseUrl, String fullUrl) { + return fullUrl.substring(baseUrl.length()).replace("/", ""); + } + + public String getVersionedSystem(String baseSystem, String version) { + if (version != null) { + return baseSystem + "_" + version; + } + return baseSystem; + } + } + public class CacheToken { private String name; private String key; private String request; public void setName(String n) { + String systemName = getSystemNameKeyGenerator().getNameForSystem(n); if (name == null) - name = n; - else if (!n.equals(name)) + name = systemName; + else if (!systemName.equals(name)) name = NAME_FOR_NO_SYSTEM; } + + public String getName() { + return name; + } } private class CacheEntry { @@ -126,7 +198,7 @@ public class TerminologyCache { public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs) { CacheToken ct = new CacheToken(); if (code.hasSystem()) - ct.name = getNameForSystem(code.getSystem()); + ct.name = getSystemNameKeyGenerator().getNameForSystem(code.getSystem()); else ct.name = NAME_FOR_NO_SYSTEM; JsonParser json = new JsonParser(); @@ -145,7 +217,7 @@ public class TerminologyCache { CacheToken ct = new CacheToken(); for (Coding c : code.getCoding()) { if (c.hasSystem()) - ct.setName(getNameForSystem(c.getSystem())); + ct.setName(c.getSystem()); } JsonParser json = new JsonParser(); json.setOutputStyle(OutputStyle.PRETTY); @@ -176,13 +248,13 @@ public class TerminologyCache { ValueSet vsc = getVSEssense(vs); for (ConceptSetComponent inc : vs.getCompose().getInclude()) if (inc.hasSystem()) - ct.setName(getNameForSystem(inc.getSystem())); + ct.setName(inc.getSystem()); for (ConceptSetComponent inc : vs.getCompose().getExclude()) if (inc.hasSystem()) - ct.setName(getNameForSystem(inc.getSystem())); + ct.setName(inc.getSystem()); for (ValueSetExpansionContainsComponent inc : vs.getExpansion().getContains()) if (inc.hasSystem()) - ct.setName(getNameForSystem(inc.getSystem())); + ct.setName(inc.getSystem()); JsonParser json = new JsonParser(); json.setOutputStyle(OutputStyle.PRETTY); try { @@ -194,34 +266,6 @@ public class TerminologyCache { return ct; } - private String getNameForSystem(String system) { - if (system.equals("http://snomed.info/sct")) - return "snomed"; - if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) - return "rxnorm"; - if (system.equals("http://loinc.org")) - return "loinc"; - if (system.equals("http://unitsofmeasure.org")) - return "ucum"; - if (system.startsWith("http://hl7.org/fhir/sid/")) - return system.substring(24).replace("/", ""); - if (system.startsWith("urn:iso:std:iso:")) - return "iso"+system.substring(16).replace(":", ""); - if (system.startsWith("http://terminology.hl7.org/CodeSystem/")) - return system.substring(38).replace("/", ""); - if (system.startsWith("http://hl7.org/fhir/")) - return system.substring(20).replace("/", ""); - if (system.equals("urn:ietf:bcp:47")) - return "lang"; - if (system.equals("urn:ietf:bcp:13")) - return "mimetypes"; - if (system.equals("urn:iso:std:iso:11073:10101")) - return "11073"; - if (system.equals("http://dicom.nema.org/resources/ontology/DCM")) - return "dicom"; - return system.replace("/", "_").replace(":", "_"); - } - public NamedCache getNamedCache(CacheToken cacheToken) { NamedCache nc = caches.get(cacheToken.name); if (nc == null) { diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/context/CacheTestUtils.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/context/CacheTestUtils.java new file mode 100644 index 000000000..059d2ec0b --- /dev/null +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/context/CacheTestUtils.java @@ -0,0 +1,8 @@ +package org.hl7.fhir.r4.context; + +import org.hl7.fhir.utilities.validation.ValidationOptions; + +public class CacheTestUtils { + public static final ValidationOptions validationOptions = new ValidationOptions().guessSystem().setVersionFlexible(false); + +} diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/context/TerminologyCacheTests.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/context/TerminologyCacheTests.java new file mode 100644 index 000000000..89ebbcc6c --- /dev/null +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/context/TerminologyCacheTests.java @@ -0,0 +1,61 @@ +package org.hl7.fhir.r4.context; + + +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.ValueSet; + +import org.hl7.fhir.utilities.tests.ResourceLoaderTests; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.io.IOException; +import java.net.URISyntaxException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +public class TerminologyCacheTests implements ResourceLoaderTests { + + private TerminologyCache createTerminologyCache() throws IOException { + Object lock = new Object(); + TerminologyCache terminologyCache = new TerminologyCache(lock, null); + return terminologyCache; + } + + @ParameterizedTest + @CsvSource({ + "http://terminology.hl7.org/CodeSystem/id,id", + "http://hl7.org/fhir/id,id", + "http://hl7.org/fhir/sid/id,id", + "http://www.nlm.nih.gov/research/umls/rxnorm,rxnorm", + "http://snomed.info/sct,snomed", + "http://www.nlm.nih.gov/research/umls/rxnorm,rxnorm", + "http://loinc.org,loinc", + "http://unitsofmeasure.org,ucum", + "urn:iso:std:iso:id,isoid", + "urn:ietf:bcp:47,lang", + "urn:ietf:bcp:13,mimetypes", + "urn:iso:std:iso:11073:10101,11073", + "my://random/system?with#chars,my___random_systemXwithXchars", + "http://dicom.nema.org/resources/ontology/DCM,dicom" + }) + public void testCacheTokenGeneration(String system, String expectedName) throws IOException, URISyntaxException { + + TerminologyCache terminologyCache = createTerminologyCache(); + ValueSet valueSet = new ValueSet(); + { + Coding coding = new Coding(); + coding.setSystem(system); + TerminologyCache.CacheToken cacheToken = terminologyCache.generateValidationToken(CacheTestUtils.validationOptions, + coding, valueSet); + assertEquals(expectedName, cacheToken.getName()); + } + { + Coding coding = new Coding(); + coding.setSystem(system + "|dummyVersion"); + TerminologyCache.CacheToken cacheToken = terminologyCache.generateValidationToken(CacheTestUtils.validationOptions, + coding, valueSet); + assertEquals(expectedName + "_dummyVersion", cacheToken.getName()); + } + } +} diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/context/TerminologyCache.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/context/TerminologyCache.java index fd6d48de4..4c5ce24b8 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/context/TerminologyCache.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/context/TerminologyCache.java @@ -1,33 +1,33 @@ package org.hl7.fhir.r4b.context; -/* - Copyright (c) 2011+, HL7, Inc. - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - */ +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + */ @@ -84,16 +84,88 @@ public class TerminologyCache { private static final String ENTRY_MARKER = "-------------------------------------------------------------------------------------"; private static final String BREAK = "####"; + private SystemNameKeyGenerator systemNameKeyGenerator = new SystemNameKeyGenerator(); + + protected SystemNameKeyGenerator getSystemNameKeyGenerator() { + return systemNameKeyGenerator; + } + + public class SystemNameKeyGenerator { + public static final String SNOMED_SCT_CODESYSTEM_URL = "http://snomed.info/sct"; + public static final String RXNORM_CODESYSTEM_URL = "http://www.nlm.nih.gov/research/umls/rxnorm"; + public static final String LOINC_CODESYSTEM_URL = "http://loinc.org"; + public static final String UCUM_CODESYSTEM_URL = "http://unitsofmeasure.org"; + + public static final String HL7_TERMINOLOGY_CODESYSTEM_BASE_URL = "http://terminology.hl7.org/CodeSystem/"; + public static final String HL7_SID_CODESYSTEM_BASE_URL = "http://hl7.org/fhir/sid/"; + public static final String HL7_FHIR_CODESYSTEM_BASE_URL = "http://hl7.org/fhir/"; + + public static final String ISO_CODESYSTEM_URN = "urn:iso:std:iso:"; + public static final String LANG_CODESYSTEM_URN = "urn:ietf:bcp:47"; + public static final String MIMETYPES_CODESYSTEM_URN = "urn:ietf:bcp:13"; + + public static final String _11073_CODESYSTEM_URN = "urn:iso:std:iso:11073:10101"; + public static final String DICOM_CODESYSTEM_URL = "http://dicom.nema.org/resources/ontology/DCM"; + + public String getNameForSystem(String system) { + final int lastPipe = system.lastIndexOf('|'); + final String systemBaseName = lastPipe == -1 ? system : system.substring(0,lastPipe); + final String systemVersion = lastPipe == -1 ? null : system.substring(lastPipe + 1); + + if (systemBaseName.equals(SNOMED_SCT_CODESYSTEM_URL)) + return getVersionedSystem("snomed", systemVersion); + if (systemBaseName.equals(RXNORM_CODESYSTEM_URL)) + return getVersionedSystem("rxnorm", systemVersion); + if (systemBaseName.equals(LOINC_CODESYSTEM_URL)) + return getVersionedSystem("loinc", systemVersion); + if (systemBaseName.equals(UCUM_CODESYSTEM_URL)) + return getVersionedSystem("ucum", systemVersion); + if (systemBaseName.startsWith(HL7_SID_CODESYSTEM_BASE_URL)) + return getVersionedSystem(normalizeBaseURL(HL7_SID_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); + if (systemBaseName.equals(_11073_CODESYSTEM_URN)) + return getVersionedSystem("11073", systemVersion); + if (systemBaseName.startsWith(ISO_CODESYSTEM_URN)) + return getVersionedSystem("iso"+systemBaseName.substring(ISO_CODESYSTEM_URN.length()).replace(":", ""), systemVersion); + if (systemBaseName.startsWith(HL7_TERMINOLOGY_CODESYSTEM_BASE_URL)) + return getVersionedSystem(normalizeBaseURL(HL7_TERMINOLOGY_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); + if (systemBaseName.startsWith(HL7_FHIR_CODESYSTEM_BASE_URL)) + return getVersionedSystem(normalizeBaseURL(HL7_FHIR_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); + if (systemBaseName.equals(LANG_CODESYSTEM_URN)) + return getVersionedSystem("lang", systemVersion); + if (systemBaseName.equals(MIMETYPES_CODESYSTEM_URN)) + return getVersionedSystem("mimetypes", systemVersion); + if (systemBaseName.equals(DICOM_CODESYSTEM_URL)) + return getVersionedSystem("dicom", systemVersion); + return getVersionedSystem(systemBaseName.replace("/", "_").replace(":", "_").replace("?", "X").replace("#", "X"), systemVersion); + } + + public String normalizeBaseURL(String baseUrl, String fullUrl) { + return fullUrl.substring(baseUrl.length()).replace("/", ""); + } + + public String getVersionedSystem(String baseSystem, String version) { + if (version != null) { + return baseSystem + "_" + version; + } + return baseSystem; + } + } + public class CacheToken { private String name; private String key; private String request; public void setName(String n) { + String systemName = getSystemNameKeyGenerator().getNameForSystem(n); if (name == null) - name = n; - else if (!n.equals(name)) + name = systemName; + else if (!systemName.equals(name)) name = NAME_FOR_NO_SYSTEM; } + + public String getName() { + return name; + } } private class CacheEntry { @@ -130,7 +202,7 @@ public class TerminologyCache { public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs) { CacheToken ct = new CacheToken(); if (code.hasSystem()) - ct.name = getNameForSystem(code.getSystem()); + ct.name = getSystemNameKeyGenerator().getNameForSystem(code.getSystem()); else ct.name = NAME_FOR_NO_SYSTEM; JsonParser json = new JsonParser(); @@ -159,7 +231,7 @@ public class TerminologyCache { CacheToken ct = new CacheToken(); for (Coding c : code.getCoding()) { if (c.hasSystem()) - ct.setName(getNameForSystem(c.getSystem())); + ct.setName(c.getSystem()); } JsonParser json = new JsonParser(); json.setOutputStyle(OutputStyle.PRETTY); @@ -190,13 +262,13 @@ public class TerminologyCache { ValueSet vsc = getVSEssense(vs); for (ConceptSetComponent inc : vs.getCompose().getInclude()) if (inc.hasSystem()) - ct.setName(getNameForSystem(inc.getSystem())); + ct.setName(inc.getSystem()); for (ConceptSetComponent inc : vs.getCompose().getExclude()) if (inc.hasSystem()) - ct.setName(getNameForSystem(inc.getSystem())); + ct.setName(inc.getSystem()); for (ValueSetExpansionContainsComponent inc : vs.getExpansion().getContains()) if (inc.hasSystem()) - ct.setName(getNameForSystem(inc.getSystem())); + ct.setName(inc.getSystem()); JsonParser json = new JsonParser(); json.setOutputStyle(OutputStyle.PRETTY); try { @@ -208,33 +280,7 @@ public class TerminologyCache { return ct; } - private String getNameForSystem(String system) { - if (system.equals("http://snomed.info/sct")) - return "snomed"; - if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) - return "rxnorm"; - if (system.equals("http://loinc.org")) - return "loinc"; - if (system.equals("http://unitsofmeasure.org")) - return "ucum"; - if (system.startsWith("http://hl7.org/fhir/sid/")) - return system.substring(24).replace("/", ""); - if (system.startsWith("urn:iso:std:iso:")) - return "iso"+system.substring(16).replace(":", ""); - if (system.startsWith("http://terminology.hl7.org/CodeSystem/")) - return system.substring(38).replace("/", ""); - if (system.startsWith("http://hl7.org/fhir/")) - return system.substring(20).replace("/", ""); - if (system.equals("urn:ietf:bcp:47")) - return "lang"; - if (system.equals("urn:ietf:bcp:13")) - return "mimetypes"; - if (system.equals("urn:iso:std:iso:11073:10101")) - return "11073"; - if (system.equals("http://dicom.nema.org/resources/ontology/DCM")) - return "dicom"; - return system.replace("/", "_").replace(":", "_").replace("?", "X").replace("#", "X"); - } + public NamedCache getNamedCache(CacheToken cacheToken) { NamedCache nc = caches.get(cacheToken.name); @@ -520,7 +566,7 @@ public class TerminologyCache { public void removeCS(String url) { synchronized (lock) { - String name = getNameForSystem(url); + String name = getSystemNameKeyGenerator().getNameForSystem(url); if (caches.containsKey(name)) { caches.remove(name); } diff --git a/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/context/CacheTestUtils.java b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/context/CacheTestUtils.java new file mode 100644 index 000000000..2fcd71fc7 --- /dev/null +++ b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/context/CacheTestUtils.java @@ -0,0 +1,8 @@ +package org.hl7.fhir.r4b.context; + +import org.hl7.fhir.utilities.validation.ValidationOptions; + +public class CacheTestUtils { + public static final ValidationOptions validationOptions = new ValidationOptions().guessSystem().setVersionFlexible(false); + +} diff --git a/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/context/TerminologyCacheTests.java b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/context/TerminologyCacheTests.java new file mode 100644 index 000000000..1a5222230 --- /dev/null +++ b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/context/TerminologyCacheTests.java @@ -0,0 +1,68 @@ +package org.hl7.fhir.r4b.context; + + +import org.hl7.fhir.r4b.model.Coding; +import org.hl7.fhir.r4b.model.ValueSet; +import org.hl7.fhir.utilities.tests.ResourceLoaderTests; + +import org.junit.jupiter.params.ParameterizedTest; + +import org.junit.jupiter.params.provider.CsvSource; + +import java.io.IOException; + +import java.net.URISyntaxException; + + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +public class TerminologyCacheTests implements ResourceLoaderTests { + + + + private TerminologyCache createTerminologyCache() throws IOException { + Object lock = new Object(); + TerminologyCache terminologyCache = new TerminologyCache(lock, null); + return terminologyCache; + } + + + + @ParameterizedTest + @CsvSource({ + "http://terminology.hl7.org/CodeSystem/id,id", + "http://hl7.org/fhir/id,id", + "http://hl7.org/fhir/sid/id,id", + "http://www.nlm.nih.gov/research/umls/rxnorm,rxnorm", + "http://snomed.info/sct,snomed", + "http://www.nlm.nih.gov/research/umls/rxnorm,rxnorm", + "http://loinc.org,loinc", + "http://unitsofmeasure.org,ucum", + "urn:iso:std:iso:id,isoid", + "urn:ietf:bcp:47,lang", + "urn:ietf:bcp:13,mimetypes", + "urn:iso:std:iso:11073:10101,11073", + "my://random/system?with#chars,my___random_systemXwithXchars", + "http://dicom.nema.org/resources/ontology/DCM,dicom" + }) + public void testCacheTokenGeneration(String system, String expectedName) throws IOException, URISyntaxException { + + TerminologyCache terminologyCache = createTerminologyCache(); + ValueSet valueSet = new ValueSet(); + { + Coding coding = new Coding(); + coding.setSystem(system); + TerminologyCache.CacheToken cacheToken = terminologyCache.generateValidationToken(CacheTestUtils.validationOptions, + coding, valueSet); + assertEquals(expectedName, cacheToken.getName()); + } + { + Coding coding = new Coding(); + coding.setSystem(system + "|dummyVersion"); + TerminologyCache.CacheToken cacheToken = terminologyCache.generateValidationToken(CacheTestUtils.validationOptions, + coding, valueSet); + assertEquals(expectedName + "_dummyVersion", cacheToken.getName()); + } + } +} 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 287d1cb9b..4ec3c1452 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 @@ -38,7 +38,6 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.util.*; -import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -85,6 +84,9 @@ public class TerminologyCache { private static final String CAPABILITY_STATEMENT_TITLE = ".capabilityStatement"; private static final String TERMINOLOGY_CAPABILITIES_TITLE = ".terminologyCapabilities"; + + private SystemNameKeyGenerator systemNameKeyGenerator = new SystemNameKeyGenerator(); + public class CacheToken { @Getter private String name; @@ -96,13 +98,79 @@ public class TerminologyCache { private boolean hasVersion; public void setName(String n) { + String systemName = getSystemNameKeyGenerator().getNameForSystem(n); if (name == null) - name = n; - else if (!n.equals(name)) + name = systemName; + else if (!systemName.equals(name)) name = NAME_FOR_NO_SYSTEM; } } + protected SystemNameKeyGenerator getSystemNameKeyGenerator() { + return systemNameKeyGenerator; + } + public class SystemNameKeyGenerator { + public static final String SNOMED_SCT_CODESYSTEM_URL = "http://snomed.info/sct"; + public static final String RXNORM_CODESYSTEM_URL = "http://www.nlm.nih.gov/research/umls/rxnorm"; + public static final String LOINC_CODESYSTEM_URL = "http://loinc.org"; + public static final String UCUM_CODESYSTEM_URL = "http://unitsofmeasure.org"; + + public static final String HL7_TERMINOLOGY_CODESYSTEM_BASE_URL = "http://terminology.hl7.org/CodeSystem/"; + public static final String HL7_SID_CODESYSTEM_BASE_URL = "http://hl7.org/fhir/sid/"; + public static final String HL7_FHIR_CODESYSTEM_BASE_URL = "http://hl7.org/fhir/"; + + public static final String ISO_CODESYSTEM_URN = "urn:iso:std:iso:"; + public static final String LANG_CODESYSTEM_URN = "urn:ietf:bcp:47"; + public static final String MIMETYPES_CODESYSTEM_URN = "urn:ietf:bcp:13"; + + public static final String _11073_CODESYSTEM_URN = "urn:iso:std:iso:11073:10101"; + public static final String DICOM_CODESYSTEM_URL = "http://dicom.nema.org/resources/ontology/DCM"; + + public String getNameForSystem(String system) { + final int lastPipe = system.lastIndexOf('|'); + final String systemBaseName = lastPipe == -1 ? system : system.substring(0,lastPipe); + final String systemVersion = lastPipe == -1 ? null : system.substring(lastPipe + 1); + + if (systemBaseName.equals(SNOMED_SCT_CODESYSTEM_URL)) + return getVersionedSystem("snomed", systemVersion); + if (systemBaseName.equals(RXNORM_CODESYSTEM_URL)) + return getVersionedSystem("rxnorm", systemVersion); + if (systemBaseName.equals(LOINC_CODESYSTEM_URL)) + return getVersionedSystem("loinc", systemVersion); + if (systemBaseName.equals(UCUM_CODESYSTEM_URL)) + return getVersionedSystem("ucum", systemVersion); + if (systemBaseName.startsWith(HL7_SID_CODESYSTEM_BASE_URL)) + return getVersionedSystem(normalizeBaseURL(HL7_SID_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); + if (systemBaseName.equals(_11073_CODESYSTEM_URN)) + return getVersionedSystem("11073", systemVersion); + if (systemBaseName.startsWith(ISO_CODESYSTEM_URN)) + return getVersionedSystem("iso"+systemBaseName.substring(ISO_CODESYSTEM_URN.length()).replace(":", ""), systemVersion); + if (systemBaseName.startsWith(HL7_TERMINOLOGY_CODESYSTEM_BASE_URL)) + return getVersionedSystem(normalizeBaseURL(HL7_TERMINOLOGY_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); + if (systemBaseName.startsWith(HL7_FHIR_CODESYSTEM_BASE_URL)) + return getVersionedSystem(normalizeBaseURL(HL7_FHIR_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); + if (systemBaseName.equals(LANG_CODESYSTEM_URN)) + return getVersionedSystem("lang", systemVersion); + if (systemBaseName.equals(MIMETYPES_CODESYSTEM_URN)) + return getVersionedSystem("mimetypes", systemVersion); + if (systemBaseName.equals(DICOM_CODESYSTEM_URL)) + return getVersionedSystem("dicom", systemVersion); + return getVersionedSystem(systemBaseName.replace("/", "_").replace(":", "_").replace("?", "X").replace("#", "X"), systemVersion); + } + + public String normalizeBaseURL(String baseUrl, String fullUrl) { + return fullUrl.substring(baseUrl.length()).replace("/", ""); + } + + public String getVersionedSystem(String baseSystem, String version) { + if (version != null) { + return baseSystem + "_" + version; + } + return baseSystem; + } + } + + private class CacheEntry { private String request; private boolean persistent; @@ -185,7 +253,7 @@ public class TerminologyCache { public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs) { CacheToken ct = new CacheToken(); if (code.hasSystem()) { - ct.name = getNameForSystem(code.getSystem()); + ct.setName(code.getSystem()); ct.hasVersion = code.hasVersion(); } else @@ -226,7 +294,7 @@ public class TerminologyCache { CacheToken ct = new CacheToken(); for (Coding c : code.getCoding()) { if (c.hasSystem()) { - ct.setName(getNameForSystem(c.getSystem())); + ct.setName(c.getSystem()); ct.hasVersion = c.hasVersion(); } } @@ -287,53 +355,31 @@ public class TerminologyCache { if (vs != null) { for (ConceptSetComponent inc : vs.getCompose().getInclude()) { if (inc.hasSystem()) { - ct.setName(getNameForSystem(inc.getSystem())); + ct.setName(inc.getSystem()); ct.hasVersion = inc.hasVersion(); } } for (ConceptSetComponent inc : vs.getCompose().getExclude()) { if (inc.hasSystem()) { - ct.setName(getNameForSystem(inc.getSystem())); + ct.setName(inc.getSystem()); ct.hasVersion = inc.hasVersion(); } } for (ValueSetExpansionContainsComponent inc : vs.getExpansion().getContains()) { if (inc.hasSystem()) { - ct.setName(getNameForSystem(inc.getSystem())); + ct.setName(inc.getSystem()); ct.hasVersion = inc.hasVersion(); } } } } - private String getNameForSystem(String system) { - if (system.equals("http://snomed.info/sct")) - return "snomed"; - if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) - return "rxnorm"; - if (system.equals("http://loinc.org")) - return "loinc"; - if (system.equals("http://unitsofmeasure.org")) - return "ucum"; - if (system.startsWith("http://hl7.org/fhir/sid/")) - return system.substring(24).replace("/", ""); - if (system.startsWith("urn:iso:std:iso:")) - return "iso"+system.substring(16).replace(":", ""); - if (system.startsWith("http://terminology.hl7.org/CodeSystem/")) - return system.substring(38).replace("/", ""); - if (system.startsWith("http://hl7.org/fhir/")) - return system.substring(20).replace("/", ""); - if (system.equals("urn:ietf:bcp:47")) - return "lang"; - if (system.equals("urn:ietf:bcp:13")) - return "mimetypes"; - if (system.equals("urn:iso:std:iso:11073:10101")) - return "11073"; - if (system.equals("http://dicom.nema.org/resources/ontology/DCM")) - return "dicom"; - return system.replace("/", "_").replace(":", "_").replace("?", "X").replace("#", "X"); + private String normalizeSystemPath(String path) { + return path.replace("/", "").replace('|','X'); } + + public NamedCache getNamedCache(CacheToken cacheToken) { final String cacheName = cacheToken.name == null ? "null" : cacheToken.name; @@ -687,7 +733,7 @@ public class TerminologyCache { public void removeCS(String url) { synchronized (lock) { - String name = getNameForSystem(url); + String name = getSystemNameKeyGenerator().getNameForSystem(url); if (caches.containsKey(name)) { caches.remove(name); } 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 ea46412e5..116eff7e5 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 @@ -33,6 +33,7 @@ import org.hl7.fhir.utilities.validation.ValidationMessage; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import com.google.gson.JsonElement; @@ -238,6 +239,8 @@ public class TerminologyCacheTests implements ResourceLoaderTests { assertTrue(cacheToken.hasVersion()); } + + @Test public void testCodableConceptCacheTokenGeneration() throws IOException, URISyntaxException { @@ -453,4 +456,41 @@ public class TerminologyCacheTests implements ResourceLoaderTests { assertEquals("http://dummy.org", extracted); } + + @ParameterizedTest + @CsvSource({ + "http://terminology.hl7.org/CodeSystem/id,id", + "http://hl7.org/fhir/id,id", + "http://hl7.org/fhir/sid/id,id", + "http://www.nlm.nih.gov/research/umls/rxnorm,rxnorm", + "http://snomed.info/sct,snomed", + "http://www.nlm.nih.gov/research/umls/rxnorm,rxnorm", + "http://loinc.org,loinc", + "http://unitsofmeasure.org,ucum", + "urn:iso:std:iso:id,isoid", + "urn:ietf:bcp:47,lang", + "urn:ietf:bcp:13,mimetypes", + "urn:iso:std:iso:11073:10101,11073", + "my://random/system?with#chars,my___random_systemXwithXchars", + "http://dicom.nema.org/resources/ontology/DCM,dicom" + }) + public void testCacheTokenGeneration(String system, String expectedName) throws IOException, URISyntaxException { + + TerminologyCache terminologyCache = createTerminologyCache(); + ValueSet valueSet = new ValueSet(); + { + Coding coding = new Coding(); + coding.setSystem(system); + TerminologyCache.CacheToken cacheToken = terminologyCache.generateValidationToken(CacheTestUtils.validationOptions, + coding, valueSet); + assertEquals(expectedName, cacheToken.getName()); + } + { + Coding coding = new Coding(); + coding.setSystem(system + "|dummyVersion"); + TerminologyCache.CacheToken cacheToken = terminologyCache.generateValidationToken(CacheTestUtils.validationOptions, + coding, valueSet); + assertEquals(expectedName + "_dummyVersion", cacheToken.getName()); + } + } } diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/v2-0360_2.7.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/v2-0360_2.7.cache new file mode 100644 index 000000000..00f2436f5 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/5.0.0/v2-0360_2.7.cache @@ -0,0 +1,12 @@ +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://terminology.hl7.org/CodeSystem/v2-0360|2.7", + "code" : "BS", + "display" : "Bachelor of Science" +}, "valueSet" :null, "lang":"null", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "versionFlexible":"true"}#### +v: { + "display" : "Bachelor of Science", + "code" : "BS", + "system" : "http://terminology.hl7.org/CodeSystem/v2-0360|2.7" +} +-------------------------------------------------------------------------------------