Merge branch 'master' into issue-2851-upload-terminology-valueset-parallel-versioning

This commit is contained in:
juan.marchionatto 2021-08-31 15:52:24 -04:00
commit 28e3265b6c
13 changed files with 272 additions and 96 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

@ -0,0 +1,6 @@
---
type: add
issue: 2933
jira: SMILE-3056
title: "Fixed a regression which causes transactions with multiple identical ifNoneExist clauses to create duplicate data."

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.batch;
import ca.uhn.fhir.jpa.batch.processor.GoldenResourceAnnotatingProcessor;
import ca.uhn.fhir.jpa.batch.processor.PidToIBaseResourceProcessor;
import ca.uhn.fhir.jpa.reindex.job.ReindexWriter;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -41,4 +42,10 @@ public class CommonBatchJobConfig {
return new GoldenResourceAnnotatingProcessor();
}
@Bean
@StepScope
public ReindexWriter reindexWriter() {
return new ReindexWriter();
}
}

View File

@ -94,6 +94,8 @@ import org.hl7.fhir.r4.model.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
@ -153,8 +155,8 @@ public abstract class BaseTransactionProcessor {
@Autowired
private InMemoryResourceMatcher myInMemoryResourceMatcher;
private ThreadPoolTaskExecutor myExecutor ;
private TaskExecutor myExecutor ;
@VisibleForTesting
public void setDaoConfig(DaoConfig theDaoConfig) {
myDaoConfig = theDaoConfig;
@ -172,16 +174,25 @@ public abstract class BaseTransactionProcessor {
@PostConstruct
public void start() {
ourLog.trace("Starting transaction processor");
myExecutor = new ThreadPoolTaskExecutor();
myExecutor.setThreadNamePrefix("bundle_batch_");
// For single thread set the value to 1
//myExecutor.setCorePoolSize(1);
//myExecutor.setMaxPoolSize(1);
myExecutor.setCorePoolSize(myDaoConfig.getBundleBatchPoolSize());
myExecutor.setMaxPoolSize(myDaoConfig.getBundleBatchMaxPoolSize());
myExecutor.setQueueCapacity(DaoConfig.DEFAULT_BUNDLE_BATCH_QUEUE_CAPACITY);
}
myExecutor.initialize();
private TaskExecutor getTaskExecutor() {
if (myExecutor == null) {
if (myDaoConfig.getBundleBatchPoolSize() > 1) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("bundle_batch_");
executor.setCorePoolSize(myDaoConfig.getBundleBatchPoolSize());
executor.setMaxPoolSize(myDaoConfig.getBundleBatchMaxPoolSize());
executor.setQueueCapacity(DaoConfig.DEFAULT_BUNDLE_BATCH_QUEUE_CAPACITY);
executor.initialize();
myExecutor = executor;
} else {
SyncTaskExecutor executor = new SyncTaskExecutor();
myExecutor = executor;
}
}
return myExecutor;
}
public <BUNDLE extends IBaseBundle> BUNDLE transaction(RequestDetails theRequestDetails, BUNDLE theRequest, boolean theNestedMode) {
@ -349,7 +360,7 @@ public abstract class BaseTransactionProcessor {
for (int i=0; i<requestEntriesSize; i++ ) {
nextRequestEntry = requestEntries.get(i);
BundleTask bundleTask = new BundleTask(completionLatch, theRequestDetails, responseMap, i, nextRequestEntry, theNestedMode);
myExecutor.submit(bundleTask);
getTaskExecutor().execute(bundleTask);
}
// waiting for all tasks to be completed
@ -1554,10 +1565,10 @@ public abstract class BaseTransactionProcessor {
return theStatusCode + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
}
public class BundleTask implements Callable<Void> {
public class BundleTask implements Runnable {
private CountDownLatch myCompletedLatch;
private ServletRequestDetails myRequestDetails;
private RequestDetails myRequestDetails;
private IBase myNextReqEntry;
private Map<Integer, Object> myResponseMap;
private int myResponseOrder;
@ -1565,7 +1576,7 @@ public abstract class BaseTransactionProcessor {
protected BundleTask(CountDownLatch theCompletedLatch, RequestDetails theRequestDetails, Map<Integer, Object> theResponseMap, int theResponseOrder, IBase theNextReqEntry, boolean theNestedMode) {
this.myCompletedLatch = theCompletedLatch;
this.myRequestDetails = (ServletRequestDetails)theRequestDetails;
this.myRequestDetails = theRequestDetails;
this.myNextReqEntry = theNextReqEntry;
this.myResponseMap = theResponseMap;
this.myResponseOrder = theResponseOrder;
@ -1573,10 +1584,8 @@ public abstract class BaseTransactionProcessor {
}
@Override
public Void call() {
public void run() {
BaseServerResponseExceptionHolder caughtEx = new BaseServerResponseExceptionHolder();
try {
IBaseBundle subRequestBundle = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode());
myVersionAdapter.addEntry(subRequestBundle, (IBase) myNextReqEntry);
@ -1609,7 +1618,6 @@ public abstract class BaseTransactionProcessor {
// checking for the parallelism
ourLog.debug("processing bacth for {} is completed", myVersionAdapter.getEntryRequestUrl((IBase)myNextReqEntry));
myCompletedLatch.countDown();
return null;
}
}
}

View File

@ -244,24 +244,28 @@ public class TransactionProcessor extends BaseTransactionProcessor {
if (orPredicates.size() > 1) {
cq.where(cb.or(orPredicates.toArray(EMPTY_PREDICATE_ARRAY)));
Map<Long, MatchUrlToResolve> hashToSearchMap = buildHashToSearchMap(searchParameterMapsToResolve);
Map<Long, List<MatchUrlToResolve>> hashToSearchMap = buildHashToSearchMap(searchParameterMapsToResolve);
TypedQuery<ResourceIndexedSearchParamToken> query = myEntityManager.createQuery(cq);
List<ResourceIndexedSearchParamToken> results = query.getResultList();
for (ResourceIndexedSearchParamToken nextResult : results) {
Optional<MatchUrlToResolve> matchedSearch = Optional.ofNullable(hashToSearchMap.get(nextResult.getHashSystemAndValue()));
Optional<List<MatchUrlToResolve>> matchedSearch = Optional.ofNullable(hashToSearchMap.get(nextResult.getHashSystemAndValue()));
if (!matchedSearch.isPresent()) {
matchedSearch = Optional.ofNullable(hashToSearchMap.get(nextResult.getHashValue()));
}
matchedSearch.ifPresent(matchUrlToResolve -> setSearchToResolvedAndPrefetchFoundResourcePid(theTransactionDetails, idsToPreFetch, nextResult, matchUrlToResolve));
matchedSearch.ifPresent(matchUrlsToResolve -> {
matchUrlsToResolve.forEach(matchUrl -> {
setSearchToResolvedAndPrefetchFoundResourcePid(theTransactionDetails, idsToPreFetch, nextResult, matchUrl);
});
});
}
//For each SP Map which did not return a result, tag it as not found.
searchParameterMapsToResolve.stream()
// No matches
.filter(match -> !match.myResolved)
.forEach(match -> {
ourLog.warn("Was unable to match url {} from database", match.myRequestUrl);
ourLog.debug("Was unable to match url {} from database", match.myRequestUrl);
theTransactionDetails.addResolvedMatchUrl(match.myRequestUrl, TransactionDetails.NOT_FOUND);
});
}
@ -322,22 +326,26 @@ public class TransactionProcessor extends BaseTransactionProcessor {
return hashPredicate;
}
private Map<Long, MatchUrlToResolve> buildHashToSearchMap(List<MatchUrlToResolve> searchParameterMapsToResolve) {
Map<Long, MatchUrlToResolve> hashToSearch = new HashMap<>();
private Map<Long, List<MatchUrlToResolve>> buildHashToSearchMap(List<MatchUrlToResolve> searchParameterMapsToResolve) {
Map<Long, List<MatchUrlToResolve>> hashToSearch = new HashMap<>();
//Build a lookup map so we don't have to iterate over the searches repeatedly.
for (MatchUrlToResolve nextSearchParameterMap : searchParameterMapsToResolve) {
if (nextSearchParameterMap.myHashSystemAndValue != null) {
hashToSearch.put(nextSearchParameterMap.myHashSystemAndValue, nextSearchParameterMap);
List<MatchUrlToResolve> matchUrlsToResolve = hashToSearch.getOrDefault(nextSearchParameterMap.myHashSystemAndValue, new ArrayList<>());
matchUrlsToResolve.add(nextSearchParameterMap);
hashToSearch.put(nextSearchParameterMap.myHashSystemAndValue, matchUrlsToResolve);
}
if (nextSearchParameterMap.myHashValue!= null) {
hashToSearch.put(nextSearchParameterMap.myHashValue, nextSearchParameterMap);
List<MatchUrlToResolve> matchUrlsToResolve = hashToSearch.getOrDefault(nextSearchParameterMap.myHashValue, new ArrayList<>());
matchUrlsToResolve.add(nextSearchParameterMap);
hashToSearch.put(nextSearchParameterMap.myHashValue, matchUrlsToResolve);
}
}
return hashToSearch;
}
private void setSearchToResolvedAndPrefetchFoundResourcePid(TransactionDetails theTransactionDetails, List<Long> idsToPreFetch, ResourceIndexedSearchParamToken nextResult, MatchUrlToResolve nextSearchParameterMap) {
ourLog.warn("Matched url {} from database", nextSearchParameterMap.myRequestUrl);
ourLog.debug("Matched url {} from database", nextSearchParameterMap.myRequestUrl);
idsToPreFetch.add(nextResult.getResourcePid());
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, nextSearchParameterMap.myResourceDefinition.getName(), nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
theTransactionDetails.addResolvedMatchUrl(nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));

View File

@ -49,6 +49,8 @@ public class ReindexEverythingJobConfig {
private StepBuilderFactory myStepBuilderFactory;
@Autowired
private JobBuilderFactory myJobBuilderFactory;
@Autowired
private ReindexWriter myReindexWriter;
@Bean(name = REINDEX_EVERYTHING_JOB_NAME)
@Lazy
@ -63,7 +65,7 @@ public class ReindexEverythingJobConfig {
return myStepBuilderFactory.get(REINDEX_EVERYTHING_STEP_NAME)
.<List<Long>, List<Long>>chunk(1)
.reader(cronologicalBatchAllResourcePidReader())
.writer(reindexWriter())
.writer(myReindexWriter)
.listener(reindexEverythingPidCountRecorderListener())
.listener(reindexEverythingPromotionListener())
.build();
@ -75,12 +77,6 @@ public class ReindexEverythingJobConfig {
return new CronologicalBatchAllResourcePidReader();
}
@Bean
@StepScope
public ReindexWriter reindexWriter() {
return new ReindexWriter();
}
@Bean
@StepScope
public PidReaderCounterListener reindexEverythingPidCountRecorderListener() {

View File

@ -51,6 +51,8 @@ public class ReindexJobConfig extends MultiUrlProcessorJobConfig {
private StepBuilderFactory myStepBuilderFactory;
@Autowired
private JobBuilderFactory myJobBuilderFactory;
@Autowired
private ReindexWriter myReindexWriter;
@Bean(name = REINDEX_JOB_NAME)
@Lazy
@ -66,18 +68,12 @@ public class ReindexJobConfig extends MultiUrlProcessorJobConfig {
return myStepBuilderFactory.get(REINDEX_URL_LIST_STEP_NAME)
.<List<Long>, List<Long>>chunk(1)
.reader(reverseCronologicalBatchResourcePidReader())
.writer(reindexWriter())
.writer(myReindexWriter)
.listener(pidCountRecorderListener())
.listener(reindexPromotionListener())
.build();
}
@Bean
@StepScope
public ReindexWriter reindexWriter() {
return new ReindexWriter();
}
@Bean
public ExecutionContextPromotionListener reindexPromotionListener() {
ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();

View File

@ -116,12 +116,16 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
myDaoConfig.setMaximumDeleteConflictQueryCount(new DaoConfig().getMaximumDeleteConflictQueryCount());
myDaoConfig.setBundleBatchPoolSize(new DaoConfig().getBundleBatchPoolSize());
myDaoConfig.setBundleBatchMaxPoolSize(new DaoConfig().getBundleBatchMaxPoolSize());
}
@BeforeEach
public void beforeDisableResultReuse() {
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
myDaoConfig.setBundleBatchPoolSize(1);
myDaoConfig.setBundleBatchMaxPoolSize(1);
}
private Bundle createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb theVerb) {

View File

@ -20,6 +20,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
@ -104,7 +105,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;
@ -127,6 +127,7 @@ import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
@ -151,12 +152,14 @@ import java.util.stream.Collectors;
import static ca.uhn.fhir.rest.api.Constants.PARAM_TYPE;
import static org.apache.commons.lang3.StringUtils.countMatches;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
@ -1357,6 +1360,34 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertThat(actual, contains(id));
}
@Test
@DisplayName("Duplicate Conditional Creates all resolve to the same match")
public void testDuplicateConditionalCreatesOnToken() throws IOException {
String inputString = IOUtils.toString(getClass().getResourceAsStream("/duplicate-conditional-create.json"), StandardCharsets.UTF_8);
Bundle firstBundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, inputString);
//Before you ask, yes, this has to be separately parsed. The reason for this is that the parameters passed to mySystemDao.transaction are _not_ immutable, so we cannot
//simply reuse the original bundle object.
Bundle duplicateBundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, inputString);
Bundle bundleResponse = mySystemDao.transaction(new SystemRequestDetails(), firstBundle);
bundleResponse.getEntry()
.forEach( entry -> assertThat(entry.getResponse().getStatus(), is(equalTo("201 Created"))));
IBundleProvider search = myOrganizationDao.search(new SearchParameterMap().setLoadSynchronous(true));
assertEquals(1, search.getAllResources().size());
//Running the bundle again should just result in 0 new resources created, as the org should already exist, and there is no update to the SR.
bundleResponse= mySystemDao.transaction(new SystemRequestDetails(), duplicateBundle);
bundleResponse.getEntry()
.forEach( entry -> {
assertThat(entry.getResponse().getStatus(), is(equalTo("200 OK")));
});
search = myOrganizationDao.search(new SearchParameterMap().setLoadSynchronous(true), new SystemRequestDetails());
assertEquals(1, search.getAllResources().size());
}
@Test
public void testIndexNoDuplicatesToken() {
Patient res = new Patient();
@ -5304,7 +5335,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

@ -117,12 +117,16 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
myDaoConfig.setAllowInlineMatchUrlReferences(false);
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
myDaoConfig.setBundleBatchPoolSize(new DaoConfig().getBundleBatchPoolSize());
myDaoConfig.setBundleBatchMaxPoolSize(new DaoConfig().getBundleBatchMaxPoolSize());
}
@BeforeEach
public void beforeDisableResultReuse() {
myInterceptorRegistry.registerInterceptor(myInterceptor);
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
myDaoConfig.setBundleBatchPoolSize(1);
myDaoConfig.setBundleBatchMaxPoolSize(1);
}
private Bundle createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb theVerb) {

View File

@ -0,0 +1,66 @@
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:4cd35592-5d4d-462b-8483-e404c023d316",
"resource": {
"resourceType": "Organization",
"identifier": [
{
"system": "https://fhir.tester.ca/NamingSystem/ca-on-health-care-facility-id",
"value": "3972"
}
]
},
"request": {
"method": "POST",
"url": "/Organization",
"ifNoneExist": "Organization?identifier=https://fhir.tester.ca/NamingSystem/ca-on-health-care-facility-id|3972"
}
},
{
"fullUrl": "urn:uuid:02643c1d-94d1-4991-a063-036fa0f57ec2",
"resource": {
"resourceType": "Organization",
"identifier": [
{
"system": "https://fhir.tester.ca/NamingSystem/ca-on-health-care-facility-id",
"value": "3972"
}
]
},
"request": {
"method": "POST",
"url": "/Organization",
"ifNoneExist": "Organization?identifier=https://fhir.tester.ca/NamingSystem/ca-on-health-care-facility-id|3972"
}
},
{
"fullUrl": "urn:uuid:8271e94f-e08b-498e-ad6d-751928c3ff99",
"resource": {
"resourceType": "ServiceRequest",
"identifier": [
{
"system": "https://fhir-tester.ca/NamingSystem/service-request-id",
"value": "1"
}
],
"performer": [
{
"reference": "urn:uuid:4cd35592-5d4d-462b-8483-e404c023d316",
"type": "Organization"
},
{
"reference": "urn:uuid:02643c1d-94d1-4991-a063-036fa0f57ec2",
"type": "Organization"
}
]
},
"request": {
"method": "PUT",
"url": "/ServiceRequest?identifier=https://fhir-tester.ca/NamingSystem/service-request-id|1"
}
}
]
}

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");