Merge pull request #2921 from hapifhir/issue-2920-lookup-lang-by-lang-only

Fix language lookup bug when omitting region during $validate
This commit is contained in:
Tadgh 2021-08-26 13:02:28 -04:00 committed by GitHub
commit 3a7d778591
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 54 deletions

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 2920
jira: SMILE-2971
title: "Previously, validation against bcp47 (urn:ietf:bcp:47) as a language would fail validation if the region was absent. This has been fixed, and the validate
operation will now correctly validate simple languages, e.g. `nl` instead of requiring `nl-DE` or `nl-NL`"

View File

@ -104,7 +104,6 @@ import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Period;
import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.Procedure;
import org.hl7.fhir.r4.model.Provenance;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Questionnaire;
@ -5304,7 +5303,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
createObservationWithEffective("YES22", "2011-01-02T00:00:00+10:00");
createObservationWithEffective("YES23", "2011-01-02T00:00:00+11:00");
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_DATE, new DateParam("2011-01-02"));

View File

@ -257,69 +257,100 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
Map<String, String> languagesMap = myLanguagesLanugageMap;
Map<String, String> regionsMap = myLanguagesRegionMap;
if (languagesMap == null || regionsMap == null) {
initializeBcp47LanguageMap();
}
ourLog.info("Loading BCP47 Language Registry");
int langRegionSeparatorIndex = StringUtils.indexOfAny(theCode, '-', '_');
boolean hasRegionAndCodeSegments = langRegionSeparatorIndex > 0;
String language;
String region;
String input = ClasspathUtil.loadResource("org/hl7/fhir/common/hapi/validation/support/registry.json");
ArrayNode map;
try {
map = (ArrayNode) new ObjectMapper().readTree(input);
} catch (JsonProcessingException e) {
throw new ConfigurationException(e);
if (hasRegionAndCodeSegments) {
language = myLanguagesLanugageMap.get(theCode.substring(0, langRegionSeparatorIndex));
region = myLanguagesRegionMap.get(theCode.substring(langRegionSeparatorIndex + 1));
if (language == null || region == null) {
//In case the user provides both a language and a region, they must both be valid for the lookup to succeed.
ourLog.warn("Couldn't find a valid bcp47 language-region combination from code: {}", theCode);
return buildNotFoundLookupCodeResult(theCode);
} else {
return buildLookupResultForLanguageAndRegion(theCode, language, region);
}
languagesMap = new HashMap<>();
regionsMap = new HashMap<>();
for (int i = 0; i < map.size(); i++) {
ObjectNode next = (ObjectNode) map.get(i);
String type = next.get("Type").asText();
if ("language".equals(type)) {
String language = next.get("Subtag").asText();
ArrayNode descriptions = (ArrayNode) next.get("Description");
String description = null;
if (descriptions.size() > 0) {
description = descriptions.get(0).asText();
}
languagesMap.put(language, description);
}
if ("region".equals(type)) {
String region = next.get("Subtag").asText();
ArrayNode descriptions = (ArrayNode) next.get("Description");
String description = null;
if (descriptions.size() > 0) {
description = descriptions.get(0).asText();
}
regionsMap.put(region, description);
}
} else {
//In case user has only provided a language, we build the lookup from only that.
language = myLanguagesLanugageMap.get(theCode);
if (language == null) {
ourLog.warn("Couldn't find a valid bcp47 language from code: {}", theCode);
return buildNotFoundLookupCodeResult(theCode);
} else {
return buildLookupResultForLanguage(theCode, language);
}
}
}
private LookupCodeResult buildLookupResultForLanguageAndRegion(@Nonnull String theOriginalCode, @Nonnull String theLanguage, @Nonnull String theRegion) {
LookupCodeResult lookupCodeResult = buildNotFoundLookupCodeResult(theOriginalCode);
lookupCodeResult.setCodeDisplay(theLanguage + " " + theRegion);
lookupCodeResult.setFound(true);
return lookupCodeResult;
}
private LookupCodeResult buildLookupResultForLanguage(@Nonnull String theOriginalCode, @Nonnull String theLanguage) {
LookupCodeResult lookupCodeResult = buildNotFoundLookupCodeResult(theOriginalCode);
lookupCodeResult.setCodeDisplay(theLanguage);
lookupCodeResult.setFound(true);
return lookupCodeResult;
}
ourLog.info("Have {} languages and {} regions", languagesMap.size(), regionsMap.size());
private LookupCodeResult buildNotFoundLookupCodeResult(@Nonnull String theOriginalCode) {
LookupCodeResult lookupCodeResult = new LookupCodeResult();
lookupCodeResult.setFound(false);
lookupCodeResult.setSearchedForSystem(LANGUAGES_CODESYSTEM_URL);
lookupCodeResult.setSearchedForCode(theOriginalCode);
return lookupCodeResult;
}
myLanguagesLanugageMap = languagesMap;
myLanguagesRegionMap = regionsMap;
private void initializeBcp47LanguageMap() {
Map<String, String> regionsMap;
Map<String, String> languagesMap;
ourLog.info("Loading BCP47 Language Registry");
String input = ClasspathUtil.loadResource("org/hl7/fhir/common/hapi/validation/support/registry.json");
ArrayNode map;
try {
map = (ArrayNode) new ObjectMapper().readTree(input);
} catch (JsonProcessingException e) {
throw new ConfigurationException(e);
}
int idx = StringUtils.indexOfAny(theCode, '-', '_');
String language = null;
String region = null;
if (idx > 0) {
language = languagesMap.get(theCode.substring(0, idx));
region = regionsMap.get(theCode.substring(idx + 1));
languagesMap = new HashMap<>();
regionsMap = new HashMap<>();
for (int i = 0; i < map.size(); i++) {
ObjectNode next = (ObjectNode) map.get(i);
String type = next.get("Type").asText();
if ("language".equals(type)) {
String language = next.get("Subtag").asText();
ArrayNode descriptions = (ArrayNode) next.get("Description");
String description = null;
if (descriptions.size() > 0) {
description = descriptions.get(0).asText();
}
languagesMap.put(language, description);
}
if ("region".equals(type)) {
String region = next.get("Subtag").asText();
ArrayNode descriptions = (ArrayNode) next.get("Description");
String description = null;
if (descriptions.size() > 0) {
description = descriptions.get(0).asText();
}
regionsMap.put(region, description);
}
}
LookupCodeResult retVal = new LookupCodeResult();
retVal.setSearchedForCode(theCode);
retVal.setSearchedForSystem(LANGUAGES_CODESYSTEM_URL);
ourLog.info("Have {} languages and {} regions", languagesMap.size(), regionsMap.size());
if (language != null && region != null) {
String display = language + " " + region;
retVal.setFound(true);
retVal.setCodeDisplay(display);
}
return retVal;
myLanguagesLanugageMap = languagesMap;
myLanguagesRegionMap = regionsMap;
}
@Nonnull

View File

@ -105,6 +105,20 @@ public class CommonCodeSystemsTerminologyServiceTest {
assertEquals("English (United States)", outcome.getDisplay());
}
@Test
public void testLanguages_CommonLanguagesVs_OnlyLanguage_NoRegion() {
IValidationSupport.LookupCodeResult nl = mySvc.lookupCode(newSupport(), "urn:ietf:bcp:47", "nl");
assertTrue(nl.isFound());
assertEquals("Dutch", nl.getCodeDisplay());
}
@Test
public void testLanguages_CommonLanguagesVs_LanguageAndRegion() {
IValidationSupport.LookupCodeResult nl = mySvc.lookupCode(newSupport(), "urn:ietf:bcp:47", "nl-NL");
assertTrue(nl.isFound());
assertEquals("Dutch Netherlands", nl.getCodeDisplay());
}
@Test
public void testLanguages_CommonLanguagesVs_BadCode() {
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(newSupport(), newOptions(), "urn:ietf:bcp:47", "FOO", null, "http://hl7.org/fhir/ValueSet/languages");