Merge branch 'master' into 2935_Search_with_trailing_percent_sign
This commit is contained in:
commit
453ef7ec1c
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
type: fix
|
||||||
|
issue: 2923
|
||||||
|
title: "$lookup operation cache was based on system and code, it becomes a defect
|
||||||
|
after adding displayLanguage support. Problem is now fixed."
|
|
@ -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."
|
||||||
|
|
|
@ -94,6 +94,8 @@ import org.hl7.fhir.r4.model.Task;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.TransactionDefinition;
|
import org.springframework.transaction.TransactionDefinition;
|
||||||
|
@ -153,8 +155,8 @@ public abstract class BaseTransactionProcessor {
|
||||||
@Autowired
|
@Autowired
|
||||||
private InMemoryResourceMatcher myInMemoryResourceMatcher;
|
private InMemoryResourceMatcher myInMemoryResourceMatcher;
|
||||||
|
|
||||||
private ThreadPoolTaskExecutor myExecutor ;
|
private TaskExecutor myExecutor ;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void setDaoConfig(DaoConfig theDaoConfig) {
|
public void setDaoConfig(DaoConfig theDaoConfig) {
|
||||||
myDaoConfig = theDaoConfig;
|
myDaoConfig = theDaoConfig;
|
||||||
|
@ -172,16 +174,25 @@ public abstract class BaseTransactionProcessor {
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void start() {
|
public void start() {
|
||||||
ourLog.trace("Starting transaction processor");
|
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) {
|
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++ ) {
|
for (int i=0; i<requestEntriesSize; i++ ) {
|
||||||
nextRequestEntry = requestEntries.get(i);
|
nextRequestEntry = requestEntries.get(i);
|
||||||
BundleTask bundleTask = new BundleTask(completionLatch, theRequestDetails, responseMap, i, nextRequestEntry, theNestedMode);
|
BundleTask bundleTask = new BundleTask(completionLatch, theRequestDetails, responseMap, i, nextRequestEntry, theNestedMode);
|
||||||
myExecutor.submit(bundleTask);
|
getTaskExecutor().execute(bundleTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
// waiting for all tasks to be completed
|
// waiting for all tasks to be completed
|
||||||
|
@ -1554,10 +1565,10 @@ public abstract class BaseTransactionProcessor {
|
||||||
return theStatusCode + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
|
return theStatusCode + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BundleTask implements Callable<Void> {
|
public class BundleTask implements Runnable {
|
||||||
|
|
||||||
private CountDownLatch myCompletedLatch;
|
private CountDownLatch myCompletedLatch;
|
||||||
private ServletRequestDetails myRequestDetails;
|
private RequestDetails myRequestDetails;
|
||||||
private IBase myNextReqEntry;
|
private IBase myNextReqEntry;
|
||||||
private Map<Integer, Object> myResponseMap;
|
private Map<Integer, Object> myResponseMap;
|
||||||
private int myResponseOrder;
|
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) {
|
protected BundleTask(CountDownLatch theCompletedLatch, RequestDetails theRequestDetails, Map<Integer, Object> theResponseMap, int theResponseOrder, IBase theNextReqEntry, boolean theNestedMode) {
|
||||||
this.myCompletedLatch = theCompletedLatch;
|
this.myCompletedLatch = theCompletedLatch;
|
||||||
this.myRequestDetails = (ServletRequestDetails)theRequestDetails;
|
this.myRequestDetails = theRequestDetails;
|
||||||
this.myNextReqEntry = theNextReqEntry;
|
this.myNextReqEntry = theNextReqEntry;
|
||||||
this.myResponseMap = theResponseMap;
|
this.myResponseMap = theResponseMap;
|
||||||
this.myResponseOrder = theResponseOrder;
|
this.myResponseOrder = theResponseOrder;
|
||||||
|
@ -1573,10 +1584,8 @@ public abstract class BaseTransactionProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void call() {
|
public void run() {
|
||||||
|
|
||||||
BaseServerResponseExceptionHolder caughtEx = new BaseServerResponseExceptionHolder();
|
BaseServerResponseExceptionHolder caughtEx = new BaseServerResponseExceptionHolder();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
IBaseBundle subRequestBundle = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode());
|
IBaseBundle subRequestBundle = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION.toCode());
|
||||||
myVersionAdapter.addEntry(subRequestBundle, (IBase) myNextReqEntry);
|
myVersionAdapter.addEntry(subRequestBundle, (IBase) myNextReqEntry);
|
||||||
|
@ -1609,7 +1618,6 @@ public abstract class BaseTransactionProcessor {
|
||||||
// checking for the parallelism
|
// checking for the parallelism
|
||||||
ourLog.debug("processing bacth for {} is completed", myVersionAdapter.getEntryRequestUrl((IBase)myNextReqEntry));
|
ourLog.debug("processing bacth for {} is completed", myVersionAdapter.getEntryRequestUrl((IBase)myNextReqEntry));
|
||||||
myCompletedLatch.countDown();
|
myCompletedLatch.countDown();
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -244,24 +244,28 @@ public class TransactionProcessor extends BaseTransactionProcessor {
|
||||||
if (orPredicates.size() > 1) {
|
if (orPredicates.size() > 1) {
|
||||||
cq.where(cb.or(orPredicates.toArray(EMPTY_PREDICATE_ARRAY)));
|
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);
|
TypedQuery<ResourceIndexedSearchParamToken> query = myEntityManager.createQuery(cq);
|
||||||
List<ResourceIndexedSearchParamToken> results = query.getResultList();
|
List<ResourceIndexedSearchParamToken> results = query.getResultList();
|
||||||
|
|
||||||
for (ResourceIndexedSearchParamToken nextResult : results) {
|
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()) {
|
if (!matchedSearch.isPresent()) {
|
||||||
matchedSearch = Optional.ofNullable(hashToSearchMap.get(nextResult.getHashValue()));
|
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.
|
//For each SP Map which did not return a result, tag it as not found.
|
||||||
searchParameterMapsToResolve.stream()
|
searchParameterMapsToResolve.stream()
|
||||||
// No matches
|
// No matches
|
||||||
.filter(match -> !match.myResolved)
|
.filter(match -> !match.myResolved)
|
||||||
.forEach(match -> {
|
.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);
|
theTransactionDetails.addResolvedMatchUrl(match.myRequestUrl, TransactionDetails.NOT_FOUND);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -322,22 +326,26 @@ public class TransactionProcessor extends BaseTransactionProcessor {
|
||||||
return hashPredicate;
|
return hashPredicate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<Long, MatchUrlToResolve> buildHashToSearchMap(List<MatchUrlToResolve> searchParameterMapsToResolve) {
|
private Map<Long, List<MatchUrlToResolve>> buildHashToSearchMap(List<MatchUrlToResolve> searchParameterMapsToResolve) {
|
||||||
Map<Long, MatchUrlToResolve> hashToSearch = new HashMap<>();
|
Map<Long, List<MatchUrlToResolve>> hashToSearch = new HashMap<>();
|
||||||
//Build a lookup map so we don't have to iterate over the searches repeatedly.
|
//Build a lookup map so we don't have to iterate over the searches repeatedly.
|
||||||
for (MatchUrlToResolve nextSearchParameterMap : searchParameterMapsToResolve) {
|
for (MatchUrlToResolve nextSearchParameterMap : searchParameterMapsToResolve) {
|
||||||
if (nextSearchParameterMap.myHashSystemAndValue != null) {
|
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) {
|
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;
|
return hashToSearch;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSearchToResolvedAndPrefetchFoundResourcePid(TransactionDetails theTransactionDetails, List<Long> idsToPreFetch, ResourceIndexedSearchParamToken nextResult, MatchUrlToResolve nextSearchParameterMap) {
|
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());
|
idsToPreFetch.add(nextResult.getResourcePid());
|
||||||
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, nextSearchParameterMap.myResourceDefinition.getName(), nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
|
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, nextSearchParameterMap.myResourceDefinition.getName(), nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
|
||||||
theTransactionDetails.addResolvedMatchUrl(nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
|
theTransactionDetails.addResolvedMatchUrl(nextSearchParameterMap.myRequestUrl, new ResourcePersistentId(nextResult.getResourcePid()));
|
||||||
|
|
|
@ -2005,9 +2005,9 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
|
||||||
result.setCodeDisplay(code.getDisplay());
|
result.setCodeDisplay(code.getDisplay());
|
||||||
|
|
||||||
for (TermConceptDesignation next : code.getDesignations()) {
|
for (TermConceptDesignation next : code.getDesignations()) {
|
||||||
IValidationSupport.ConceptDesignation designation = new IValidationSupport.ConceptDesignation();
|
|
||||||
// filter out the designation based on displayLanguage if any
|
// filter out the designation based on displayLanguage if any
|
||||||
if (isDisplayLanguageMatch(theDisplayLanguage, next.getLanguage())) {
|
if (isDisplayLanguageMatch(theDisplayLanguage, next.getLanguage())) {
|
||||||
|
IValidationSupport.ConceptDesignation designation = new IValidationSupport.ConceptDesignation();
|
||||||
designation.setLanguage(next.getLanguage());
|
designation.setLanguage(next.getLanguage());
|
||||||
designation.setUseSystem(next.getUseSystem());
|
designation.setUseSystem(next.getUseSystem());
|
||||||
designation.setUseCode(next.getUseCode());
|
designation.setUseCode(next.getUseCode());
|
||||||
|
|
|
@ -116,12 +116,16 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
|
||||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||||
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
|
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
|
||||||
myDaoConfig.setMaximumDeleteConflictQueryCount(new DaoConfig().getMaximumDeleteConflictQueryCount());
|
myDaoConfig.setMaximumDeleteConflictQueryCount(new DaoConfig().getMaximumDeleteConflictQueryCount());
|
||||||
|
myDaoConfig.setBundleBatchPoolSize(new DaoConfig().getBundleBatchPoolSize());
|
||||||
|
myDaoConfig.setBundleBatchMaxPoolSize(new DaoConfig().getBundleBatchMaxPoolSize());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void beforeDisableResultReuse() {
|
public void beforeDisableResultReuse() {
|
||||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||||
|
myDaoConfig.setBundleBatchPoolSize(1);
|
||||||
|
myDaoConfig.setBundleBatchMaxPoolSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bundle createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb theVerb) {
|
private Bundle createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb theVerb) {
|
||||||
|
|
|
@ -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.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||||
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
|
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.MatchUrlService;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap.EverythingModeEnum;
|
||||||
|
@ -126,6 +127,7 @@ import org.hl7.fhir.r4.model.ValueSet;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.ArgumentMatchers;
|
import org.mockito.ArgumentMatchers;
|
||||||
|
@ -150,12 +152,14 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static ca.uhn.fhir.rest.api.Constants.PARAM_TYPE;
|
import static ca.uhn.fhir.rest.api.Constants.PARAM_TYPE;
|
||||||
import static org.apache.commons.lang3.StringUtils.countMatches;
|
import static org.apache.commons.lang3.StringUtils.countMatches;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.empty;
|
import static org.hamcrest.Matchers.empty;
|
||||||
import static org.hamcrest.Matchers.endsWith;
|
import static org.hamcrest.Matchers.endsWith;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.hasItem;
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
import static org.hamcrest.Matchers.hasItems;
|
import static org.hamcrest.Matchers.hasItems;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
@ -1356,6 +1360,34 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
assertThat(actual, contains(id));
|
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
|
@Test
|
||||||
public void testIndexNoDuplicatesToken() {
|
public void testIndexNoDuplicatesToken() {
|
||||||
Patient res = new Patient();
|
Patient res = new Patient();
|
||||||
|
|
|
@ -117,12 +117,16 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
|
||||||
myDaoConfig.setAllowInlineMatchUrlReferences(false);
|
myDaoConfig.setAllowInlineMatchUrlReferences(false);
|
||||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||||
myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
|
myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
|
||||||
|
myDaoConfig.setBundleBatchPoolSize(new DaoConfig().getBundleBatchPoolSize());
|
||||||
|
myDaoConfig.setBundleBatchMaxPoolSize(new DaoConfig().getBundleBatchMaxPoolSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void beforeDisableResultReuse() {
|
public void beforeDisableResultReuse() {
|
||||||
myInterceptorRegistry.registerInterceptor(myInterceptor);
|
myInterceptorRegistry.registerInterceptor(myInterceptor);
|
||||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||||
|
myDaoConfig.setBundleBatchPoolSize(1);
|
||||||
|
myDaoConfig.setBundleBatchMaxPoolSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bundle createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb theVerb) {
|
private Bundle createInputTransactionWithPlaceholderIdInMatchUrl(HTTPVerb theVerb) {
|
||||||
|
|
|
@ -45,33 +45,15 @@ public class ResourceProviderR4CodeSystemDesignationTest extends BaseResourcePro
|
||||||
String resp = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam);
|
String resp = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam);
|
||||||
ourLog.info(resp);
|
ourLog.info(resp);
|
||||||
|
|
||||||
|
//-- The designations should have de-AT and default language
|
||||||
List<ParametersParameterComponent> parameterList = respParam.getParameter();
|
List<ParametersParameterComponent> parameterList = respParam.getParameter();
|
||||||
|
|
||||||
|
verifyParameterList(parameterList);
|
||||||
List<ParametersParameterComponent> designationList = getDesignations(parameterList);
|
List<ParametersParameterComponent> designationList = getDesignations(parameterList);
|
||||||
|
// should be de-AT and default
|
||||||
assertEquals("display", respParam.getParameter().get(0).getName());
|
assertEquals(2, designationList.size());
|
||||||
assertEquals(("Systolic blood pressure 12 hour minimum"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
|
verifyDesignationDeAT(designationList.get(0));
|
||||||
|
verifyDesignationNoLanguage(designationList.get(1));
|
||||||
assertEquals("abstract", respParam.getParameter().get(1).getName());
|
|
||||||
assertEquals(false, ((BooleanType) respParam.getParameter().get(1).getValue()).getValue());
|
|
||||||
|
|
||||||
//-- designationList
|
|
||||||
assertEquals(2, designationList.size());
|
|
||||||
|
|
||||||
// 1. de-AT:Systolic blood pressure 12 hour minimum
|
|
||||||
ParametersParameterComponent designation = designationList.get(0);
|
|
||||||
assertEquals("language", designation.getPart().get(0).getName());
|
|
||||||
assertEquals("de-AT", designation.getPart().get(0).getValue().toString());
|
|
||||||
assertEquals("value", designation.getPart().get(2).getName());
|
|
||||||
assertEquals("de-AT:Systolic blood pressure 12 hour minimum", designation.getPart().get(2).getValue().toString());
|
|
||||||
|
|
||||||
// 2. Systolic blood pressure 12 hour minimum (no language)
|
|
||||||
designation = designationList.get(1);
|
|
||||||
assertEquals("language", designation.getPart().get(0).getName());
|
|
||||||
assertNull(designation.getPart().get(0).getValue());
|
|
||||||
assertEquals("value", designation.getPart().get(2).getName());
|
|
||||||
assertEquals("Systolic blood pressure 12 hour minimum", designation.getPart().get(2).getValue().toString());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,25 +71,14 @@ public class ResourceProviderR4CodeSystemDesignationTest extends BaseResourcePro
|
||||||
String resp = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam);
|
String resp = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam);
|
||||||
ourLog.info(resp);
|
ourLog.info(resp);
|
||||||
|
|
||||||
|
//-- The designations should have default language only
|
||||||
List<ParametersParameterComponent> parameterList = respParam.getParameter();
|
List<ParametersParameterComponent> parameterList = respParam.getParameter();
|
||||||
|
|
||||||
|
verifyParameterList(parameterList);
|
||||||
List<ParametersParameterComponent> designationList = getDesignations(parameterList);
|
List<ParametersParameterComponent> designationList = getDesignations(parameterList);
|
||||||
|
// should be default only
|
||||||
assertEquals("display", respParam.getParameter().get(0).getName());
|
assertEquals(1, designationList.size());
|
||||||
assertEquals(("Systolic blood pressure 12 hour minimum"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
|
verifyDesignationNoLanguage(designationList.get(0));
|
||||||
|
|
||||||
assertEquals("abstract", respParam.getParameter().get(1).getName());
|
|
||||||
assertEquals(false, ((BooleanType) respParam.getParameter().get(1).getValue()).getValue());
|
|
||||||
|
|
||||||
//-- designationList
|
|
||||||
assertEquals(1, designationList.size());
|
|
||||||
|
|
||||||
// 1. Systolic blood pressure 12 hour minimum (no language)
|
|
||||||
ParametersParameterComponent designation = designationList.get(0);
|
|
||||||
assertEquals("language", designation.getPart().get(0).getName());
|
|
||||||
assertNull(designation.getPart().get(0).getValue());
|
|
||||||
assertEquals("value", designation.getPart().get(2).getName());
|
|
||||||
assertEquals("Systolic blood pressure 12 hour minimum", designation.getPart().get(2).getValue().toString());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,41 +95,145 @@ public class ResourceProviderR4CodeSystemDesignationTest extends BaseResourcePro
|
||||||
String resp = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam);
|
String resp = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam);
|
||||||
ourLog.info(resp);
|
ourLog.info(resp);
|
||||||
|
|
||||||
|
//-- The designations should have all languages and the default language
|
||||||
List<ParametersParameterComponent> parameterList = respParam.getParameter();
|
List<ParametersParameterComponent> parameterList = respParam.getParameter();
|
||||||
|
|
||||||
|
verifyParameterList(parameterList);
|
||||||
List<ParametersParameterComponent> designationList = getDesignations(parameterList);
|
List<ParametersParameterComponent> designationList = getDesignations(parameterList);
|
||||||
|
// designation should be fr-FR, De-AT and default
|
||||||
assertEquals("display", respParam.getParameter().get(0).getName());
|
assertEquals(3, designationList.size());
|
||||||
assertEquals(("Systolic blood pressure 12 hour minimum"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
|
verifyDesignationfrFR(designationList.get(0));
|
||||||
|
verifyDesignationDeAT(designationList.get(1));
|
||||||
assertEquals("abstract", respParam.getParameter().get(1).getName());
|
verifyDesignationNoLanguage(designationList.get(2));
|
||||||
assertEquals(false, ((BooleanType) respParam.getParameter().get(1).getValue()).getValue());
|
|
||||||
|
|
||||||
//-- designationList
|
}
|
||||||
assertEquals(3, designationList.size());
|
|
||||||
|
@Test
|
||||||
|
public void testLookupWithDisplayLanguageCaching() {
|
||||||
|
|
||||||
// 1. fr-FR:Systolic blood pressure 12 hour minimum
|
//-- first call with de-AT
|
||||||
ParametersParameterComponent designation = designationList.get(0);
|
Parameters respParam = myClient
|
||||||
|
.operation()
|
||||||
|
.onType(CodeSystem.class)
|
||||||
|
.named("lookup")
|
||||||
|
.withParameter(Parameters.class, "code", new CodeType("8494-7"))
|
||||||
|
.andParameter("system", new UriType(CS_ACME_URL))
|
||||||
|
.andParameter("displayLanguage",new CodeType("de-AT"))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
String resp = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam);
|
||||||
|
ourLog.info(resp);
|
||||||
|
|
||||||
|
//-- The designations should have de-AT and default language
|
||||||
|
List<ParametersParameterComponent> parameterList = respParam.getParameter();
|
||||||
|
|
||||||
|
verifyParameterList(parameterList);
|
||||||
|
List<ParametersParameterComponent> designationList = getDesignations(parameterList);
|
||||||
|
// should be de-AT and default
|
||||||
|
assertEquals(2, designationList.size());
|
||||||
|
verifyDesignationDeAT(designationList.get(0));
|
||||||
|
verifyDesignationNoLanguage(designationList.get(1));
|
||||||
|
|
||||||
|
//-- second call with zh-CN (not-exist)
|
||||||
|
respParam = myClient
|
||||||
|
.operation()
|
||||||
|
.onType(CodeSystem.class)
|
||||||
|
.named("lookup")
|
||||||
|
.withParameter(Parameters.class, "code", new CodeType("8494-7"))
|
||||||
|
.andParameter("system", new UriType(CS_ACME_URL))
|
||||||
|
.andParameter("displayLanguage",new CodeType("zh-CN"))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
resp = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam);
|
||||||
|
ourLog.info(resp);
|
||||||
|
|
||||||
|
//-- The designations should have default language only
|
||||||
|
parameterList = respParam.getParameter();
|
||||||
|
|
||||||
|
verifyParameterList(parameterList);
|
||||||
|
designationList = getDesignations(parameterList);
|
||||||
|
// should be default only
|
||||||
|
assertEquals(1, designationList.size());
|
||||||
|
verifyDesignationNoLanguage(designationList.get(0));
|
||||||
|
|
||||||
|
//-- third call with no language
|
||||||
|
respParam = myClient
|
||||||
|
.operation()
|
||||||
|
.onType(CodeSystem.class)
|
||||||
|
.named("lookup")
|
||||||
|
.withParameter(Parameters.class, "code", new CodeType("8494-7"))
|
||||||
|
.andParameter("system", new UriType(CS_ACME_URL))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
resp = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam);
|
||||||
|
ourLog.info(resp);
|
||||||
|
|
||||||
|
//-- The designations should have all languages and the default language
|
||||||
|
parameterList = respParam.getParameter();
|
||||||
|
|
||||||
|
verifyParameterList(parameterList);
|
||||||
|
designationList = getDesignations(parameterList);
|
||||||
|
// designation should be fr-FR, De-AT and default
|
||||||
|
assertEquals(3, designationList.size());
|
||||||
|
verifyDesignationfrFR(designationList.get(0));
|
||||||
|
verifyDesignationDeAT(designationList.get(1));
|
||||||
|
verifyDesignationNoLanguage(designationList.get(2));
|
||||||
|
|
||||||
|
//-- forth call with fr-FR
|
||||||
|
respParam = myClient
|
||||||
|
.operation()
|
||||||
|
.onType(CodeSystem.class)
|
||||||
|
.named("lookup")
|
||||||
|
.withParameter(Parameters.class, "code", new CodeType("8494-7"))
|
||||||
|
.andParameter("system", new UriType(CS_ACME_URL))
|
||||||
|
.andParameter("displayLanguage",new CodeType("fr-FR"))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
resp = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam);
|
||||||
|
ourLog.info(resp);
|
||||||
|
|
||||||
|
//-- The designations should have fr-FR languages and the default language
|
||||||
|
parameterList = respParam.getParameter();
|
||||||
|
|
||||||
|
verifyParameterList(parameterList);
|
||||||
|
designationList = getDesignations(parameterList);
|
||||||
|
// designation should be fr-FR, default
|
||||||
|
assertEquals(2, designationList.size());
|
||||||
|
verifyDesignationfrFR(designationList.get(0));
|
||||||
|
verifyDesignationNoLanguage(designationList.get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void verifyParameterList(List<ParametersParameterComponent> parameterList) {
|
||||||
|
assertEquals("display", parameterList.get(0).getName());
|
||||||
|
assertEquals(("Systolic blood pressure 12 hour minimum"),
|
||||||
|
((StringType) parameterList.get(0).getValue()).getValue());
|
||||||
|
|
||||||
|
assertEquals("abstract", parameterList.get(1).getName());
|
||||||
|
assertEquals(false, ((BooleanType) parameterList.get(1).getValue()).getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyDesignationfrFR(ParametersParameterComponent designation) {
|
||||||
assertEquals("language", designation.getPart().get(0).getName());
|
assertEquals("language", designation.getPart().get(0).getName());
|
||||||
assertEquals("fr-FR", designation.getPart().get(0).getValue().toString());
|
assertEquals("fr-FR", designation.getPart().get(0).getValue().toString());
|
||||||
assertEquals("value", designation.getPart().get(2).getName());
|
assertEquals("value", designation.getPart().get(2).getName());
|
||||||
assertEquals("fr-FR:Systolic blood pressure 12 hour minimum", designation.getPart().get(2).getValue().toString());
|
assertEquals("fr-FR:Systolic blood pressure 12 hour minimum", designation.getPart().get(2).getValue().toString());
|
||||||
|
}
|
||||||
// 2. de-AT:Systolic blood pressure 12 hour minimum
|
|
||||||
designation = designationList.get(1);
|
private void verifyDesignationDeAT(ParametersParameterComponent designation) {
|
||||||
assertEquals("language", designation.getPart().get(0).getName());
|
assertEquals("language", designation.getPart().get(0).getName());
|
||||||
assertEquals("de-AT", designation.getPart().get(0).getValue().toString());
|
assertEquals("de-AT", designation.getPart().get(0).getValue().toString());
|
||||||
assertEquals("value", designation.getPart().get(2).getName());
|
assertEquals("value", designation.getPart().get(2).getName());
|
||||||
assertEquals("de-AT:Systolic blood pressure 12 hour minimum", designation.getPart().get(2).getValue().toString());
|
assertEquals("de-AT:Systolic blood pressure 12 hour minimum", designation.getPart().get(2).getValue().toString());
|
||||||
|
}
|
||||||
// 3. Systolic blood pressure 12 hour minimum (no language)
|
|
||||||
designation = designationList.get(2);
|
private void verifyDesignationNoLanguage(ParametersParameterComponent designation) {
|
||||||
assertEquals("language", designation.getPart().get(0).getName());
|
assertEquals("language", designation.getPart().get(0).getName());
|
||||||
assertNull(designation.getPart().get(0).getValue());
|
assertNull(designation.getPart().get(0).getValue());
|
||||||
assertEquals("value", designation.getPart().get(2).getName());
|
assertEquals("value", designation.getPart().get(2).getName());
|
||||||
assertEquals("Systolic blood pressure 12 hour minimum", designation.getPart().get(2).getValue().toString());
|
assertEquals("Systolic blood pressure 12 hour minimum", designation.getPart().get(2).getValue().toString());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ParametersParameterComponent> getDesignations(List<ParametersParameterComponent> parameterList) {
|
private List<ParametersParameterComponent> getDesignations(List<ParametersParameterComponent> parameterList) {
|
||||||
|
|
||||||
List<ParametersParameterComponent> designationList = new ArrayList<>();
|
List<ParametersParameterComponent> designationList = new ArrayList<>();
|
||||||
|
@ -168,6 +243,5 @@ public class ResourceProviderR4CodeSystemDesignationTest extends BaseResourcePro
|
||||||
designationList.add(parameter);
|
designationList.add(parameter);
|
||||||
}
|
}
|
||||||
return designationList;
|
return designationList;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -112,7 +112,7 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) {
|
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) {
|
||||||
String key = "lookupCode " + theSystem + " " + theCode;
|
String key = "lookupCode " + theSystem + " " + theCode + " " + defaultIfBlank(theDisplayLanguage, "NO_LANG");
|
||||||
return loadFromCache(myLookupCodeCache, key, t -> super.lookupCode(theValidationSupportContext, theSystem, theCode, theDisplayLanguage));
|
return loadFromCache(myLookupCodeCache, key, t -> super.lookupCode(theValidationSupportContext, theSystem, theCode, theDisplayLanguage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue