Merge branch 'master' into 2849_add_new_mdm_param

This commit is contained in:
Nick Goupinets 2021-09-02 17:42:41 -04:00
commit ee65a24260
20 changed files with 472 additions and 164 deletions

View File

@ -75,6 +75,7 @@ public enum VersionEnum {
V5_4_1,
V5_4_2,
V5_5_0,
V5_5_1,
V5_6_0
;

View File

@ -0,0 +1,3 @@
---
release-date: "2021-08-30"
codename: "Quasar"

View File

@ -0,0 +1,4 @@
---
type: fix
issue: 2793
title: "Previously, when using the Expunge Everything operation, caches could retain old invalid values. This has been corrected. Thanks to [Ben Li-Sauerwine](https://github.com/theGOTOguy) for the fix!"

View File

@ -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."

View File

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

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 2935
jira: SMILE-3022
title: "No resource returned when search with percent sign. Problem is now fixed"

View File

@ -38,7 +38,7 @@ section.server_plain.title=Plain Server
page.server_plain.server_types=REST Server Types
page.server_plain.introduction=Plain Server Introduction
page.server_plain.get_started=Get Started ⚡
page.server_plain.resource_providers=Resource Providers and Plan Providers
page.server_plain.resource_providers=Resource Providers and Plain Providers
page.server_plain.rest_operations=REST Operations: Overview
page.server_plain.rest_operations_search=REST Operations: Search
page.server_plain.rest_operations_operations=REST Operations: Extended Operations

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

@ -65,6 +65,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
@ -100,6 +101,9 @@ public class ExpungeEverythingService {
private TransactionTemplate myTxTemplate;
@Autowired
private MemoryCacheService myMemoryCacheService;
@PostConstruct
public void initTxTemplate() {
myTxTemplate = new TransactionTemplate(myPlatformTransactionManager);
@ -122,37 +126,37 @@ public class ExpungeEverythingService {
counter.addAndGet(doExpungeEverythingQuery("UPDATE " + TermCodeSystem.class.getSimpleName() + " d SET d.myCurrentVersion = null"));
return null;
});
counter.addAndGet(expungeEverythingByType(NpmPackageVersionResourceEntity.class));
counter.addAndGet(expungeEverythingByType(NpmPackageVersionEntity.class));
counter.addAndGet(expungeEverythingByType(NpmPackageEntity.class));
counter.addAndGet(expungeEverythingByType(SearchParamPresent.class));
counter.addAndGet(expungeEverythingByType(BulkImportJobFileEntity.class));
counter.addAndGet(expungeEverythingByType(BulkImportJobEntity.class));
counter.addAndGet(expungeEverythingByType(ForcedId.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamDate.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamNumber.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamQuantity.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamQuantityNormalized.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamString.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamToken.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamUri.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedSearchParamCoords.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedComboStringUnique.class));
counter.addAndGet(expungeEverythingByType(ResourceIndexedComboTokenNonUnique.class));
counter.addAndGet(expungeEverythingByType(ResourceLink.class));
counter.addAndGet(expungeEverythingByType(SearchResult.class));
counter.addAndGet(expungeEverythingByType(SearchInclude.class));
counter.addAndGet(expungeEverythingByType(TermValueSetConceptDesignation.class));
counter.addAndGet(expungeEverythingByType(TermValueSetConcept.class));
counter.addAndGet(expungeEverythingByType(TermValueSet.class));
counter.addAndGet(expungeEverythingByType(TermConceptParentChildLink.class));
counter.addAndGet(expungeEverythingByType(TermConceptMapGroupElementTarget.class));
counter.addAndGet(expungeEverythingByType(TermConceptMapGroupElement.class));
counter.addAndGet(expungeEverythingByType(TermConceptMapGroup.class));
counter.addAndGet(expungeEverythingByType(TermConceptMap.class));
counter.addAndGet(expungeEverythingByType(TermConceptProperty.class));
counter.addAndGet(expungeEverythingByType(TermConceptDesignation.class));
counter.addAndGet(expungeEverythingByType(TermConcept.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(NpmPackageVersionResourceEntity.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(NpmPackageVersionEntity.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(NpmPackageEntity.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(SearchParamPresent.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(BulkImportJobFileEntity.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(BulkImportJobEntity.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ForcedId.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamDate.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamNumber.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamQuantity.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamQuantityNormalized.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamString.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamToken.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamUri.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamCoords.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedComboStringUnique.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedComboTokenNonUnique.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceLink.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(SearchResult.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(SearchInclude.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermValueSetConceptDesignation.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermValueSetConcept.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermValueSet.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptParentChildLink.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptMapGroupElementTarget.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptMapGroupElement.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptMapGroup.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptMap.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptProperty.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptDesignation.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConcept.class));
myTxTemplate.execute(t -> {
for (TermCodeSystem next : myEntityManager.createQuery("SELECT c FROM " + TermCodeSystem.class.getName() + " c", TermCodeSystem.class).getResultList()) {
next.setCurrentVersion(null);
@ -160,52 +164,66 @@ public class ExpungeEverythingService {
}
return null;
});
counter.addAndGet(expungeEverythingByType(TermCodeSystemVersion.class));
counter.addAndGet(expungeEverythingByType(TermCodeSystem.class));
counter.addAndGet(expungeEverythingByType(SubscriptionTable.class));
counter.addAndGet(expungeEverythingByType(ResourceHistoryTag.class));
counter.addAndGet(expungeEverythingByType(ResourceTag.class));
counter.addAndGet(expungeEverythingByType(TagDefinition.class));
counter.addAndGet(expungeEverythingByType(ResourceHistoryProvenanceEntity.class));
counter.addAndGet(expungeEverythingByType(ResourceHistoryTable.class));
counter.addAndGet(expungeEverythingByType(ResourceTable.class));
counter.addAndGet(expungeEverythingByType(PartitionEntity.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermCodeSystemVersion.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermCodeSystem.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(SubscriptionTable.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceHistoryTag.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceTag.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(TagDefinition.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceHistoryProvenanceEntity.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceHistoryTable.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceTable.class));
counter.addAndGet(expungeEverythingByTypeWithoutPurging(PartitionEntity.class));
myTxTemplate.execute(t -> {
counter.addAndGet(doExpungeEverythingQuery("DELETE from " + Search.class.getSimpleName() + " d"));
return null;
});
purgeAllCaches();
ourLog.info("COMPLETED GLOBAL $expunge - Deleted {} rows", counter.get());
}
private void purgeAllCaches() {
myTxTemplate.execute(t -> {
myMemoryCacheService.invalidateAllCaches();
return null;
});
}
private int expungeEverythingByTypeWithoutPurging(Class<?> theEntityType) {
int outcome = 0;
while (true) {
StopWatch sw = new StopWatch();
@SuppressWarnings("ConstantConditions")
int count = myTxTemplate.execute(t -> {
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
CriteriaQuery<?> cq = cb.createQuery(theEntityType);
cq.from(theEntityType);
TypedQuery<?> query = myEntityManager.createQuery(cq);
query.setMaxResults(1000);
List<?> results = query.getResultList();
for (Object result : results) {
myEntityManager.remove(result);
}
return results.size();
});
outcome += count;
if (count == 0) {
break;
}
ourLog.info("Have deleted {} entities of type {} in {}", outcome, theEntityType.getSimpleName(), sw.toString());
}
return outcome;
}
public int expungeEverythingByType(Class<?> theEntityType) {
int outcome = 0;
while (true) {
StopWatch sw = new StopWatch();
@SuppressWarnings("ConstantConditions")
int count = myTxTemplate.execute(t -> {
CriteriaBuilder cb = myEntityManager.getCriteriaBuilder();
CriteriaQuery<?> cq = cb.createQuery(theEntityType);
cq.from(theEntityType);
TypedQuery<?> query = myEntityManager.createQuery(cq);
query.setMaxResults(1000);
List<?> results = query.getResultList();
for (Object result : results) {
myEntityManager.remove(result);
}
return results.size();
});
outcome += count;
if (count == 0) {
break;
}
ourLog.info("Have deleted {} entities of type {} in {}", outcome, theEntityType.getSimpleName(), sw.toString());
}
return outcome;
int result = expungeEverythingByTypeWithoutPurging(theEntityType);
purgeAllCaches();
return result;
}
private int doExpungeEverythingQuery(String theQuery) {

View File

@ -225,15 +225,15 @@ public class StringPredicateBuilder extends BaseSearchParamPredicateBuilder {
}
public static String createLeftAndRightMatchLikeExpression(String likeExpression) {
return "%" + likeExpression.replace("%", "[%]") + "%";
return "%" + likeExpression.replace("%", "\\%") + "%";
}
public static String createLeftMatchLikeExpression(String likeExpression) {
return likeExpression.replace("%", "[%]") + "%";
return likeExpression.replace("%", "\\%") + "%";
}
public static String createRightMatchLikeExpression(String likeExpression) {
return "%" + likeExpression.replace("%", "[%]");
return "%" + likeExpression.replace("%", "\\%");
}

View File

@ -2005,9 +2005,9 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
result.setCodeDisplay(code.getDisplay());
for (TermConceptDesignation next : code.getDesignations()) {
IValidationSupport.ConceptDesignation designation = new IValidationSupport.ConceptDesignation();
// filter out the designation based on displayLanguage if any
if (isDisplayLanguageMatch(theDisplayLanguage, next.getLanguage())) {
IValidationSupport.ConceptDesignation designation = new IValidationSupport.ConceptDesignation();
designation.setLanguage(next.getLanguage());
designation.setUseSystem(next.getUseSystem());
designation.setUseCode(next.getUseCode());

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

@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.test.concurrency.PointcutLatch;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Meta;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -40,6 +41,8 @@ public class ExpungeHookTest extends BaseJpaDstu3Test {
@BeforeEach
public void before() {
myDaoConfig.setExpungeEnabled(true);
myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ALPHANUMERIC);
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
myInterceptorService.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING, myEverythingLatch);
myInterceptorService.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE, myExpungeResourceLatch);
}
@ -65,6 +68,42 @@ public class ExpungeHookTest extends BaseJpaDstu3Test {
assertPatientGone(id);
}
@Test
public void expungeEverythingAndRecreate() throws InterruptedException {
// Create a patient.
Patient thePatient = new Patient();
thePatient.setId("ABC123");
Meta theMeta = new Meta();
theMeta.addProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient");
thePatient.setMeta(theMeta);
IIdType id = myPatientDao.update(thePatient, mySrd).getId();
assertNotNull(myPatientDao.read(id));
// Expunge it directly.
myPatientDao.delete(id);
ExpungeOptions options = new ExpungeOptions();
options.setExpungeEverything(true);
options.setExpungeDeletedResources(true);
options.setExpungeOldVersions(true);
myPatientDao.expunge(id.toUnqualifiedVersionless(), options, mySrd);
assertPatientGone(id);
// Create it a second time.
myPatientDao.update(thePatient, mySrd);
assertNotNull(myPatientDao.read(id));
// Expunge everything with the service.
myEverythingLatch.setExpectedCount(1);
myExpungeService.expunge(null, null, null, options, mySrd);
myEverythingLatch.awaitExpected();
assertPatientGone(id);
// Create it a third time.
myPatientDao.update(thePatient, mySrd);
assertNotNull(myPatientDao.read(id));
}
private void assertPatientGone(IIdType theId) {
try {
myPatientDao.read(theId);

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;
@ -126,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;
@ -150,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;
@ -1356,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();

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

@ -45,33 +45,15 @@ public class ResourceProviderR4CodeSystemDesignationTest extends BaseResourcePro
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);
assertEquals("display", respParam.getParameter().get(0).getName());
assertEquals(("Systolic blood pressure 12 hour minimum"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
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());
// should be de-AT and default
assertEquals(2, designationList.size());
verifyDesignationDeAT(designationList.get(0));
verifyDesignationNoLanguage(designationList.get(1));
}
@ -89,25 +71,14 @@ public class ResourceProviderR4CodeSystemDesignationTest extends BaseResourcePro
String resp = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
//-- The designations should have default language only
List<ParametersParameterComponent> parameterList = respParam.getParameter();
verifyParameterList(parameterList);
List<ParametersParameterComponent> designationList = getDesignations(parameterList);
assertEquals("display", respParam.getParameter().get(0).getName());
assertEquals(("Systolic blood pressure 12 hour minimum"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
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());
// should be default only
assertEquals(1, designationList.size());
verifyDesignationNoLanguage(designationList.get(0));
}
@ -124,41 +95,145 @@ public class ResourceProviderR4CodeSystemDesignationTest extends BaseResourcePro
String resp = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
//-- The designations should have all languages and the default language
List<ParametersParameterComponent> parameterList = respParam.getParameter();
verifyParameterList(parameterList);
List<ParametersParameterComponent> designationList = getDesignations(parameterList);
assertEquals("display", respParam.getParameter().get(0).getName());
assertEquals(("Systolic blood pressure 12 hour minimum"), ((StringType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("abstract", respParam.getParameter().get(1).getName());
assertEquals(false, ((BooleanType) respParam.getParameter().get(1).getValue()).getValue());
// 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));
//-- designationList
assertEquals(3, designationList.size());
}
@Test
public void testLookupWithDisplayLanguageCaching() {
// 1. fr-FR:Systolic blood pressure 12 hour minimum
ParametersParameterComponent designation = designationList.get(0);
//-- first call with de-AT
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("fr-FR", designation.getPart().get(0).getValue().toString());
assertEquals("value", designation.getPart().get(2).getName());
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("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());
// 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());
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());
}
private List<ParametersParameterComponent> getDesignations(List<ParametersParameterComponent> parameterList) {
List<ParametersParameterComponent> designationList = new ArrayList<>();
@ -168,6 +243,5 @@ public class ResourceProviderR4CodeSystemDesignationTest extends BaseResourcePro
designationList.add(parameter);
}
return designationList;
}
}

View File

@ -295,7 +295,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myCaptureQueriesListener.logSelectQueries();
}
@Test
public void testSearchWithContainsLowerCase() {
myDaoConfig.setAllowContainsSearches(true);
@ -333,6 +332,37 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
@Test
public void testSearchWithPercentSign() {
myDaoConfig.setAllowContainsSearches(true);
Patient pt1 = new Patient();
pt1.addName().setFamily("Smith%");
String pt1id = myPatientDao.create(pt1).getId().toUnqualifiedVersionless().getValue();
Bundle output = myClient
.search()
.forResource("Patient")
.where(Patient.NAME.contains().value("Smith%"))
.returnBundle(Bundle.class)
.execute();
List<String> ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList());
assertThat(ids, containsInAnyOrder(pt1id));
Patient pt2 = new Patient();
pt2.addName().setFamily("Sm%ith");
String pt2id = myPatientDao.create(pt2).getId().toUnqualifiedVersionless().getValue();
output = myClient
.search()
.forResource("Patient")
.where(Patient.NAME.contains().value("Sm%ith"))
.returnBundle(Bundle.class)
.execute();
ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList());
assertThat(ids, containsInAnyOrder(pt2id));
}
@Test
public void testSearchWithDateInvalid() throws IOException {
HttpGet get = new HttpGet(ourServerBase + "/Condition?onset-date=junk");

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

@ -112,7 +112,7 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
@Override
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));
}