Partition aware search cleanup (#4706)

* Partition aware search cleanup

* Compile fixes

* Build fixes

* HAPI FHIR version bump

* License

* License header

* Tests
This commit is contained in:
James Agnew 2023-04-02 11:50:20 -04:00 committed by GitHub
parent 4fbeeccda4
commit a2bc9a7212
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
96 changed files with 454 additions and 190 deletions

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,14 +4,16 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId> <artifactId>hapi-fhir-bom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>HAPI FHIR BOM</name> <name>HAPI FHIR BOM</name>
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId> <artifactId>hapi-fhir-cli</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom</relativePath> <relativePath>../../hapi-deployable-pom</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -11,7 +11,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -120,7 +120,6 @@ public class SearchConfig {
mySearchBuilderFactory, mySearchBuilderFactory,
mySynchronousSearchSvc, mySynchronousSearchSvc,
myPersistedJpaBundleProviderFactory, myPersistedJpaBundleProviderFactory,
myRequestPartitionHelperService,
mySearchParamRegistry, mySearchParamRegistry,
mySearchStrategyFactory, mySearchStrategyFactory,
exceptionService(), exceptionService(),

View File

@ -249,7 +249,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
Optional<Search> searchOpt = myTxService Optional<Search> searchOpt = myTxService
.withRequest(myRequest) .withRequest(myRequest)
.withRequestPartitionId(myRequestPartitionId) .withRequestPartitionId(myRequestPartitionId)
.execute(() -> mySearchCacheSvc.fetchByUuid(myUuid)); .execute(() -> mySearchCacheSvc.fetchByUuid(myUuid, myRequestPartitionId));
if (!searchOpt.isPresent()) { if (!searchOpt.isPresent()) {
return false; return false;
} }
@ -404,7 +404,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
if (mySearchEntity.getSearchType() == SearchTypeEnum.HISTORY) { if (mySearchEntity.getSearchType() == SearchTypeEnum.HISTORY) {
return null; return null;
} else { } else {
return mySearchCoordinatorSvc.getSearchTotal(myUuid, myRequest).orElse(null); return mySearchCoordinatorSvc.getSearchTotal(myUuid, myRequest, myRequestPartitionId).orElse(null);
} }
} }

View File

@ -39,7 +39,6 @@ import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.dao.JpaPid; import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.search.builder.StorageInterceptorHooksFacade; import ca.uhn.fhir.jpa.search.builder.StorageInterceptorHooksFacade;
import ca.uhn.fhir.jpa.search.builder.tasks.SearchContinuationTask; import ca.uhn.fhir.jpa.search.builder.tasks.SearchContinuationTask;
import ca.uhn.fhir.jpa.search.builder.tasks.SearchTask; import ca.uhn.fhir.jpa.search.builder.tasks.SearchTask;
@ -113,7 +112,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
private final BeanFactory myBeanFactory; private final BeanFactory myBeanFactory;
private final ConcurrentHashMap<String, SearchTask> myIdToSearchTask = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, SearchTask> myIdToSearchTask = new ConcurrentHashMap<>();
private final Consumer<String> myOnRemoveSearchTask = (theId) -> myIdToSearchTask.remove(theId); private final Consumer<String> myOnRemoveSearchTask = myIdToSearchTask::remove;
private final StorageInterceptorHooksFacade myStorageInterceptorHooks; private final StorageInterceptorHooksFacade myStorageInterceptorHooks;
private Integer myLoadingThrottleForUnitTests = null; private Integer myLoadingThrottleForUnitTests = null;
@ -135,7 +134,6 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
SearchBuilderFactory<JpaPid> theSearchBuilderFactory, SearchBuilderFactory<JpaPid> theSearchBuilderFactory,
ISynchronousSearchSvc theSynchronousSearchSvc, ISynchronousSearchSvc theSynchronousSearchSvc,
PersistedJpaBundleProviderFactory thePersistedJpaBundleProviderFactory, PersistedJpaBundleProviderFactory thePersistedJpaBundleProviderFactory,
IRequestPartitionHelperSvc theRequestPartitionHelperService,
ISearchParamRegistry theSearchParamRegistry, ISearchParamRegistry theSearchParamRegistry,
SearchStrategyFactory theSearchStrategyFactory, SearchStrategyFactory theSearchStrategyFactory,
ExceptionService theExceptionSvc, ExceptionService theExceptionSvc,
@ -245,7 +243,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
} }
Callable<Search> searchCallback = () -> mySearchCacheSvc Callable<Search> searchCallback = () -> mySearchCacheSvc
.fetchByUuid(theUuid) .fetchByUuid(theUuid, theRequestPartitionId)
.orElseThrow(() -> myExceptionSvc.newUnknownSearchException(theUuid)); .orElseThrow(() -> myExceptionSvc.newUnknownSearchException(theUuid));
search = myTxService search = myTxService
.withRequest(theRequestDetails) .withRequest(theRequestDetails)
@ -271,7 +269,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
// start a new pass // start a new pass
if (search.getStatus() == SearchStatusEnum.PASSCMPLET) { if (search.getStatus() == SearchStatusEnum.PASSCMPLET) {
ourLog.trace("Going to try to start next search"); ourLog.trace("Going to try to start next search");
Optional<Search> newSearch = mySearchCacheSvc.tryToMarkSearchAsInProgress(search); Optional<Search> newSearch = mySearchCacheSvc.tryToMarkSearchAsInProgress(search, theRequestPartitionId);
if (newSearch.isPresent()) { if (newSearch.isPresent()) {
ourLog.trace("Launching new search"); ourLog.trace("Launching new search");
search = newSearch.get(); search = newSearch.get();
@ -440,7 +438,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
} }
@Override @Override
public Optional<Integer> getSearchTotal(String theUuid, @Nullable RequestDetails theRequestDetails) { public Optional<Integer> getSearchTotal(String theUuid, @Nullable RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId) {
SearchTask task = myIdToSearchTask.get(theUuid); SearchTask task = myIdToSearchTask.get(theUuid);
if (task != null) { if (task != null) {
return Optional.ofNullable(task.awaitInitialSync()); return Optional.ofNullable(task.awaitInitialSync());
@ -450,7 +448,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
* In case there is no running search, if the total is listed as accurate we know one is coming * In case there is no running search, if the total is listed as accurate we know one is coming
* so let's wait a bit for it to show up * so let's wait a bit for it to show up
*/ */
Optional<Search> search = myTxService.withRequest(theRequestDetails).execute(() -> mySearchCacheSvc.fetchByUuid(theUuid)); Optional<Search> search = myTxService.withRequest(theRequestDetails).execute(() -> mySearchCacheSvc.fetchByUuid(theUuid, theRequestPartitionId));
if (search.isPresent()) { if (search.isPresent()) {
Optional<SearchParameterMap> searchParameterMap = search.get().getSearchParameterMap(); Optional<SearchParameterMap> searchParameterMap = search.get().getSearchParameterMap();
if (searchParameterMap.isPresent() && searchParameterMap.get().getSearchTotalMode() == SearchTotalModeEnum.ACCURATE) { if (searchParameterMap.isPresent() && searchParameterMap.get().getSearchTotalMode() == SearchTotalModeEnum.ACCURATE) {
@ -461,7 +459,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc<JpaPid> {
return Optional.of(search.get().getTotalCount()); return Optional.of(search.get().getTotalCount());
} }
} }
search = mySearchCacheSvc.fetchByUuid(theUuid); search = mySearchCacheSvc.fetchByUuid(theUuid, theRequestPartitionId);
} }
} }
} }

View File

@ -19,6 +19,7 @@
*/ */
package ca.uhn.fhir.jpa.search; package ca.uhn.fhir.jpa.search;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.model.sched.HapiJob; import ca.uhn.fhir.jpa.model.sched.HapiJob;
import ca.uhn.fhir.jpa.model.sched.IHasScheduledJobs; import ca.uhn.fhir.jpa.model.sched.IHasScheduledJobs;
@ -50,7 +51,7 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc, IHas
@Override @Override
@Transactional(propagation = Propagation.NEVER) @Transactional(propagation = Propagation.NEVER)
public void pollForStaleSearchesAndDeleteThem() { public void pollForStaleSearchesAndDeleteThem() {
mySearchCacheSvc.pollForStaleSearchesAndDeleteThem(); mySearchCacheSvc.pollForStaleSearchesAndDeleteThem(RequestPartitionId.allPartitions());
} }
@Override @Override

View File

@ -474,7 +474,7 @@ public class SearchTask implements Callable<Void> {
} }
private void doSaveSearch() { private void doSaveSearch() {
Search newSearch = mySearchCacheSvc.save(mySearch); Search newSearch = mySearchCacheSvc.save(mySearch, myRequestPartitionId);
// mySearchDao.save is not supposed to return null, but in unit tests // mySearchDao.save is not supposed to return null, but in unit tests
// it can if the mock search dao isn't set up to handle that // it can if the mock search dao isn't set up to handle that

View File

@ -25,6 +25,8 @@ import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao; import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.system.HapiSystemProperties; import ca.uhn.fhir.system.HapiSystemProperties;
@ -38,11 +40,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import java.time.Instant; import java.time.Instant;
import java.util.Collection; import java.util.Collection;
@ -58,7 +57,7 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc {
*/ */
public static final int DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_STMT = 500; public static final int DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_STMT = 500;
public static final int DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_PAS = 20000; public static final int DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_PAS = 20000;
public static final long SEARCH_CLEANUP_JOB_INTERVAL_MILLIS = 10 * DateUtils.MILLIS_PER_SECOND; public static final long SEARCH_CLEANUP_JOB_INTERVAL_MILLIS = DateUtils.MILLIS_PER_MINUTE;
public static final int DEFAULT_MAX_DELETE_CANDIDATES_TO_FIND = 2000; public static final int DEFAULT_MAX_DELETE_CANDIDATES_TO_FIND = 2000;
private static final Logger ourLog = LoggerFactory.getLogger(DatabaseSearchCacheSvcImpl.class); private static final Logger ourLog = LoggerFactory.getLogger(DatabaseSearchCacheSvcImpl.class);
private static int ourMaximumResultsToDeleteInOneStatement = DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_STMT; private static int ourMaximumResultsToDeleteInOneStatement = DEFAULT_MAX_RESULTS_TO_DELETE_IN_ONE_STMT;
@ -78,7 +77,7 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc {
@Autowired @Autowired
private ISearchIncludeDao mySearchIncludeDao; private ISearchIncludeDao mySearchIncludeDao;
@Autowired @Autowired
private PlatformTransactionManager myTxManager; private IHapiTransactionService myTransactionService;
@Autowired @Autowired
private JpaStorageSettings myStorageSettings; private JpaStorageSettings myStorageSettings;
@ -87,36 +86,40 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc {
myCutoffSlack = theCutoffSlack; myCutoffSlack = theCutoffSlack;
} }
@Transactional(propagation = Propagation.REQUIRED)
@Override @Override
public Search save(Search theSearch) { public Search save(Search theSearch, RequestPartitionId theRequestPartitionId) {
Search newSearch = mySearchDao.save(theSearch); return myTransactionService
return newSearch; .withSystemRequestOnPartition(theRequestPartitionId)
.execute(() -> mySearchDao.save(theSearch));
} }
@Override @Override
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
public Optional<Search> fetchByUuid(String theUuid) { public Optional<Search> fetchByUuid(String theUuid, RequestPartitionId theRequestPartitionId) {
Validate.notBlank(theUuid); Validate.notBlank(theUuid);
return mySearchDao.findByUuidAndFetchIncludes(theUuid); return myTransactionService
.withSystemRequestOnPartition(theRequestPartitionId)
.execute(() -> mySearchDao.findByUuidAndFetchIncludes(theUuid));
} }
void setSearchDaoForUnitTest(ISearchDao theSearchDao) { void setSearchDaoForUnitTest(ISearchDao theSearchDao) {
mySearchDao = theSearchDao; mySearchDao = theSearchDao;
} }
void setTxManagerForUnitTest(PlatformTransactionManager theTxManager) { void setTransactionServiceForUnitTest(IHapiTransactionService theTransactionService) {
myTxManager = theTxManager; myTransactionService = theTransactionService;
} }
@Override @Override
public Optional<Search> tryToMarkSearchAsInProgress(Search theSearch) { public Optional<Search> tryToMarkSearchAsInProgress(Search theSearch, RequestPartitionId theRequestPartitionId) {
ourLog.trace("Going to try to change search status from {} to {}", theSearch.getStatus(), SearchStatusEnum.LOADING); ourLog.trace("Going to try to change search status from {} to {}", theSearch.getStatus(), SearchStatusEnum.LOADING);
try { try {
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); return myTransactionService
txTemplate.afterPropertiesSet(); .withSystemRequest()
return txTemplate.execute(t -> { .withRequestPartitionId(theRequestPartitionId)
.withPropagation(Propagation.REQUIRES_NEW)
.execute(t -> {
Search search = mySearchDao.findById(theSearch.getId()).orElse(theSearch); Search search = mySearchDao.findById(theSearch.getId()).orElse(theSearch);
if (search.getStatus() != SearchStatusEnum.PASSCMPLET) { if (search.getStatus() != SearchStatusEnum.PASSCMPLET) {
@ -135,6 +138,8 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc {
@Override @Override
public Optional<Search> findCandidatesForReuse(String theResourceType, String theQueryString, Instant theCreatedAfter, RequestPartitionId theRequestPartitionId) { public Optional<Search> findCandidatesForReuse(String theResourceType, String theQueryString, Instant theCreatedAfter, RequestPartitionId theRequestPartitionId) {
HapiTransactionService.requireTransaction();
String queryString = Search.createSearchQueryStringForStorage(theQueryString, theRequestPartitionId); String queryString = Search.createSearchQueryStringForStorage(theQueryString, theRequestPartitionId);
int hashCode = queryString.hashCode(); int hashCode = queryString.hashCode();
@ -151,9 +156,10 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc {
return Optional.empty(); return Optional.empty();
} }
@Transactional(propagation = Propagation.NEVER)
@Override @Override
public void pollForStaleSearchesAndDeleteThem() { public void pollForStaleSearchesAndDeleteThem(RequestPartitionId theRequestPartitionId) {
HapiTransactionService.noTransactionAllowed();
if (!myStorageSettings.isExpireSearchResults()) { if (!myStorageSettings.isExpireSearchResults()) {
return; return;
} }
@ -170,29 +176,37 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc {
ourLog.debug("Searching for searches which are before {}", cutoff); ourLog.debug("Searching for searches which are before {}", cutoff);
TransactionTemplate tt = new TransactionTemplate(myTxManager);
// Mark searches as deleted if they should be // Mark searches as deleted if they should be
final Slice<Long> toMarkDeleted = tt.execute(theStatus -> final Slice<Long> toMarkDeleted = myTransactionService
.withSystemRequestOnPartition(theRequestPartitionId)
.execute(theStatus ->
mySearchDao.findWhereCreatedBefore(cutoff, new Date(), PageRequest.of(0, ourMaximumSearchesToCheckForDeletionCandidacy)) mySearchDao.findWhereCreatedBefore(cutoff, new Date(), PageRequest.of(0, ourMaximumSearchesToCheckForDeletionCandidacy))
); );
assert toMarkDeleted != null; assert toMarkDeleted != null;
for (final Long nextSearchToDelete : toMarkDeleted) { for (final Long nextSearchToDelete : toMarkDeleted) {
ourLog.debug("Deleting search with PID {}", nextSearchToDelete); ourLog.debug("Deleting search with PID {}", nextSearchToDelete);
tt.execute(t -> { myTransactionService
.withSystemRequest()
.withRequestPartitionId(theRequestPartitionId)
.execute(t -> {
mySearchDao.updateDeleted(nextSearchToDelete, true); mySearchDao.updateDeleted(nextSearchToDelete, true);
return null; return null;
}); });
} }
// Delete searches that are marked as deleted // Delete searches that are marked as deleted
final Slice<Long> toDelete = tt.execute(theStatus -> final Slice<Long> toDelete = myTransactionService
.withSystemRequestOnPartition(theRequestPartitionId)
.execute(theStatus ->
mySearchDao.findDeleted(PageRequest.of(0, ourMaximumSearchesToCheckForDeletionCandidacy)) mySearchDao.findDeleted(PageRequest.of(0, ourMaximumSearchesToCheckForDeletionCandidacy))
); );
assert toDelete != null; assert toDelete != null;
for (final Long nextSearchToDelete : toDelete) { for (final Long nextSearchToDelete : toDelete) {
ourLog.debug("Deleting search with PID {}", nextSearchToDelete); ourLog.debug("Deleting search with PID {}", nextSearchToDelete);
tt.execute(t -> { myTransactionService
.withSystemRequest()
.withRequestPartitionId(theRequestPartitionId)
.execute(t -> {
deleteSearch(nextSearchToDelete); deleteSearch(nextSearchToDelete);
return null; return null;
}); });
@ -201,7 +215,10 @@ public class DatabaseSearchCacheSvcImpl implements ISearchCacheSvc {
int count = toDelete.getContent().size(); int count = toDelete.getContent().size();
if (count > 0) { if (count > 0) {
if (ourLog.isDebugEnabled() || HapiSystemProperties.isTestModeEnabled()) { if (ourLog.isDebugEnabled() || HapiSystemProperties.isTestModeEnabled()) {
Long total = tt.execute(t -> mySearchDao.count()); Long total = myTransactionService
.withSystemRequest()
.withRequestPartitionId(theRequestPartitionId)
.execute(t -> mySearchDao.count());
ourLog.debug("Deleted {} searches, {} remaining", count, total); ourLog.debug("Deleted {} searches, {} remaining", count, total);
} }
} }

View File

@ -34,7 +34,7 @@ public interface ISearchCacheSvc {
* @param theSearch The search to store * @param theSearch The search to store
* @return Returns a copy of the search as it was saved. Callers should use the returned Search object for any further processing. * @return Returns a copy of the search as it was saved. Callers should use the returned Search object for any further processing.
*/ */
Search save(Search theSearch); Search save(Search theSearch, RequestPartitionId theRequestPartitionId);
/** /**
* Fetch a search using its UUID. The search should be fully loaded when it is returned (i.e. includes are fetched, so that access to its * Fetch a search using its UUID. The search should be fully loaded when it is returned (i.e. includes are fetched, so that access to its
@ -43,7 +43,7 @@ public interface ISearchCacheSvc {
* @param theUuid The search UUID * @param theUuid The search UUID
* @return The search if it exists * @return The search if it exists
*/ */
Optional<Search> fetchByUuid(String theUuid); Optional<Search> fetchByUuid(String theUuid, RequestPartitionId theRequestPartitionId);
/** /**
* TODO: this is perhaps an inappropriate responsibility for this service * TODO: this is perhaps an inappropriate responsibility for this service
@ -59,7 +59,7 @@ public interface ISearchCacheSvc {
* succeeded in marking it). If the search doesn't exist or some other error occurred, an exception will be thrown * succeeded in marking it). If the search doesn't exist or some other error occurred, an exception will be thrown
* instead of {@link Optional#empty()} * instead of {@link Optional#empty()}
*/ */
Optional<Search> tryToMarkSearchAsInProgress(Search theSearch); Optional<Search> tryToMarkSearchAsInProgress(Search theSearch, RequestPartitionId theRequestPartitionId);
/** /**
* Look for any existing searches matching the given resource type and query string. * Look for any existing searches matching the given resource type and query string.
@ -82,5 +82,5 @@ public interface ISearchCacheSvc {
* if they have some other mechanism for expiring stale results other than manually looking for them * if they have some other mechanism for expiring stale results other than manually looking for them
* and deleting them. * and deleting them.
*/ */
void pollForStaleSearchesAndDeleteThem(); void pollForStaleSearchesAndDeleteThem(RequestPartitionId theRequestPartitionId);
} }

View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -3,7 +3,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -134,7 +134,6 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc {
mySearchBuilderFactory, mySearchBuilderFactory,
mySynchronousSearchSvc, mySynchronousSearchSvc,
myPersistedJpaBundleProviderFactory, myPersistedJpaBundleProviderFactory,
myPartitionHelperSvc,
null, // search param registry null, // search param registry
mySearchStrategyFactory, mySearchStrategyFactory,
myExceptionSvc, myExceptionSvc,
@ -187,7 +186,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc {
when(mySearchBuilder.createQuery(any(), any(), any(), nullable(RequestPartitionId.class))).thenReturn(iter); when(mySearchBuilder.createQuery(any(), any(), any(), nullable(RequestPartitionId.class))).thenReturn(iter);
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
when(mySearchCacheSvc.save(any())).thenAnswer(t -> { when(mySearchCacheSvc.save(any(), any())).thenAnswer(t -> {
Search search = t.getArgument(0, Search.class); Search search = t.getArgument(0, Search.class);
myCurrentSearch = search; myCurrentSearch = search;
return search; return search;
@ -207,7 +206,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc {
assertEquals("799", resources.get(789).getIdElement().getValueAsString()); assertEquals("799", resources.get(789).getIdElement().getValueAsString());
ArgumentCaptor<Search> searchCaptor = ArgumentCaptor.forClass(Search.class); ArgumentCaptor<Search> searchCaptor = ArgumentCaptor.forClass(Search.class);
verify(mySearchCacheSvc, atLeastOnce()).save(searchCaptor.capture()); verify(mySearchCacheSvc, atLeastOnce()).save(searchCaptor.capture(), any());
assertEquals(790, allResults.size()); assertEquals(790, allResults.size());
assertEquals(10, allResults.get(0).getId()); assertEquals(10, allResults.get(0).getId());
@ -224,10 +223,10 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc {
myCurrentSearch.setStatus(SearchStatusEnum.PASSCMPLET); myCurrentSearch.setStatus(SearchStatusEnum.PASSCMPLET);
myCurrentSearch.setNumFound(10); myCurrentSearch.setNumFound(10);
when(mySearchCacheSvc.fetchByUuid(any())).thenAnswer(t -> Optional.ofNullable(myCurrentSearch)); when(mySearchCacheSvc.fetchByUuid(any(), any())).thenAnswer(t -> Optional.ofNullable(myCurrentSearch));
when(mySearchCacheSvc.tryToMarkSearchAsInProgress(any())).thenAnswer(t -> { when(mySearchCacheSvc.tryToMarkSearchAsInProgress(any(), any())).thenAnswer(t -> {
when(mySearchCacheSvc.fetchByUuid(any())).thenAnswer(t2 -> Optional.empty()); when(mySearchCacheSvc.fetchByUuid(any(), any())).thenAnswer(t2 -> Optional.empty());
return Optional.empty(); return Optional.empty();
}); });
@ -248,7 +247,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc {
myCurrentSearch.setStatus(SearchStatusEnum.PASSCMPLET); myCurrentSearch.setStatus(SearchStatusEnum.PASSCMPLET);
myCurrentSearch.setNumFound(10); myCurrentSearch.setNumFound(10);
when(mySearchCacheSvc.fetchByUuid(any())).thenAnswer(t -> { when(mySearchCacheSvc.fetchByUuid(any(), any())).thenAnswer(t -> {
sleepAtLeast(100); sleepAtLeast(100);
return Optional.ofNullable(myCurrentSearch); return Optional.ofNullable(myCurrentSearch);
}); });
@ -373,7 +372,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc {
List<JpaPid> pids = createPidSequence(800); List<JpaPid> pids = createPidSequence(800);
IResultIterator iter = new SlowIterator(pids.iterator(), 2); IResultIterator iter = new SlowIterator(pids.iterator(), 2);
when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(RequestPartitionId.class))).thenReturn(iter); when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(RequestPartitionId.class))).thenReturn(iter);
when(mySearchCacheSvc.save(any())).thenAnswer(t -> { when(mySearchCacheSvc.save(any(), any())).thenAnswer(t -> {
ourLog.info("Saving search"); ourLog.info("Saving search");
return t.getArgument(0, Search.class); return t.getArgument(0, Search.class);
}); });
@ -386,7 +385,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc {
assertEquals(790, result.size()); assertEquals(790, result.size());
ArgumentCaptor<Search> searchCaptor = ArgumentCaptor.forClass(Search.class); ArgumentCaptor<Search> searchCaptor = ArgumentCaptor.forClass(Search.class);
verify(mySearchCacheSvc, atLeast(1)).save(searchCaptor.capture()); verify(mySearchCacheSvc, atLeast(1)).save(searchCaptor.capture(), any());
Search search = searchCaptor.getValue(); Search search = searchCaptor.getValue();
assertEquals(SearchTypeEnum.SEARCH, search.getSearchType()); assertEquals(SearchTypeEnum.SEARCH, search.getSearchType());
@ -450,7 +449,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc {
search.setStatus(SearchStatusEnum.LOADING); search.setStatus(SearchStatusEnum.LOADING);
search.setSearchParameterMap(new SearchParameterMap()); search.setSearchParameterMap(new SearchParameterMap());
when(mySearchCacheSvc.fetchByUuid(eq(uuid))).thenReturn(Optional.of(search)); when(mySearchCacheSvc.fetchByUuid(eq(uuid), any())).thenReturn(Optional.of(search));
doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any());
PersistedJpaBundleProvider provider; PersistedJpaBundleProvider provider;
@ -567,7 +566,7 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc {
search.setStatus(SearchStatusEnum.FINISHED); search.setStatus(SearchStatusEnum.FINISHED);
search.setNumFound(100); search.setNumFound(100);
search.setTotalCount(100); search.setTotalCount(100);
when(mySearchCacheSvc.fetchByUuid(eq("0000-1111"))).thenReturn(Optional.of(search)); when(mySearchCacheSvc.fetchByUuid(eq("0000-1111"), any())).thenReturn(Optional.of(search));
when(mySearchResultCacheSvc.fetchResultPids(any(), anyInt(), anyInt(), any(), any())).thenReturn(null); when(mySearchResultCacheSvc.fetchResultPids(any(), anyInt(), anyInt(), any(), any())).thenReturn(null);
@ -594,9 +593,9 @@ public class SearchCoordinatorSvcImplTest extends BaseSearchSvc {
search.setStatus(SearchStatusEnum.PASSCMPLET); search.setStatus(SearchStatusEnum.PASSCMPLET);
search.setNumFound(5); search.setNumFound(5);
search.setSearchParameterMap(new SearchParameterMap()); search.setSearchParameterMap(new SearchParameterMap());
when(mySearchCacheSvc.fetchByUuid(eq("0000-1111"))).thenReturn(Optional.of(search)); when(mySearchCacheSvc.fetchByUuid(eq("0000-1111"), any())).thenReturn(Optional.of(search));
when(mySearchCacheSvc.tryToMarkSearchAsInProgress(any())).thenAnswer(t -> { when(mySearchCacheSvc.tryToMarkSearchAsInProgress(any(), any())).thenAnswer(t -> {
search.setStatus(SearchStatusEnum.LOADING); search.setStatus(SearchStatusEnum.LOADING);
return Optional.of(search); return Optional.of(search);
}); });

View File

@ -6,7 +6,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4281,7 +4281,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
search.setStatus(SearchStatusEnum.FAILED); search.setStatus(SearchStatusEnum.FAILED);
search.setFailureCode(500); search.setFailureCode(500);
search.setFailureMessage("FOO"); search.setFailureMessage("FOO");
mySearchCacheSvc.save(search); mySearchCacheSvc.save(search, RequestPartitionId.defaultPartition());
}); });
IBundleProvider results = myEncounterDao.search(map); IBundleProvider results = myEncounterDao.search(map);

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
@ -85,7 +86,7 @@ public class SearchCoordinatorSvcImplTest extends BaseJpaR4Test {
assertEquals(30, mySearchResultDao.count()); assertEquals(30, mySearchResultDao.count());
}); });
myDatabaseCacheSvc.pollForStaleSearchesAndDeleteThem(); myDatabaseCacheSvc.pollForStaleSearchesAndDeleteThem(RequestPartitionId.allPartitions());
runInTransaction(()->{ runInTransaction(()->{
// We should delete up to 10, but 3 don't get deleted since they have too many results to delete in one pass // We should delete up to 10, but 3 don't get deleted since they have too many results to delete in one pass
assertEquals(13, mySearchDao.count()); assertEquals(13, mySearchDao.count());
@ -94,7 +95,7 @@ public class SearchCoordinatorSvcImplTest extends BaseJpaR4Test {
assertEquals(15, mySearchResultDao.count()); assertEquals(15, mySearchResultDao.count());
}); });
myDatabaseCacheSvc.pollForStaleSearchesAndDeleteThem(); myDatabaseCacheSvc.pollForStaleSearchesAndDeleteThem(RequestPartitionId.allPartitions());
runInTransaction(()->{ runInTransaction(()->{
// Once again we attempt to delete 10, but the first 3 don't get deleted and still remain // Once again we attempt to delete 10, but the first 3 don't get deleted and still remain
// (total is 6 because 3 weren't deleted, and they blocked another 3 that might have been) // (total is 6 because 3 weren't deleted, and they blocked another 3 that might have been)
@ -103,7 +104,7 @@ public class SearchCoordinatorSvcImplTest extends BaseJpaR4Test {
assertEquals(0, mySearchResultDao.count()); assertEquals(0, mySearchResultDao.count());
}); });
myDatabaseCacheSvc.pollForStaleSearchesAndDeleteThem(); myDatabaseCacheSvc.pollForStaleSearchesAndDeleteThem(RequestPartitionId.allPartitions());
runInTransaction(()->{ runInTransaction(()->{
assertEquals(0, mySearchDao.count()); assertEquals(0, mySearchDao.count());
assertEquals(0, mySearchDao.countDeleted()); assertEquals(0, mySearchDao.countDeleted());

View File

@ -6,7 +6,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -6,7 +6,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -1,3 +1,22 @@
/*-
* #%L
* HAPI FHIR JPA Server Test Utilities
* %%
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.jpa.test; package ca.uhn.fhir.jpa.test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;

View File

@ -1,3 +1,22 @@
/*-
* #%L
* HAPI FHIR JPA Server Test Utilities
* %%
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.jpa.test; package ca.uhn.fhir.jpa.test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;

View File

@ -1,6 +1,8 @@
package ca.uhn.fhir.jpa.search.cache; package ca.uhn.fhir.jpa.search.cache;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.tx.NonTransactionalHapiTransactionService;
import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
@ -9,7 +11,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.transaction.PlatformTransactionManager;
import java.util.Optional; import java.util.Optional;
@ -30,14 +31,11 @@ public class DatabaseSearchCacheSvcImplTest {
@Mock @Mock
private ISearchDao mySearchDao; private ISearchDao mySearchDao;
@Mock
private PlatformTransactionManager myTxManager;
@BeforeEach @BeforeEach
public void before() { public void before() {
mySvc = new DatabaseSearchCacheSvcImpl(); mySvc = new DatabaseSearchCacheSvcImpl();
mySvc.setSearchDaoForUnitTest(mySearchDao); mySvc.setSearchDaoForUnitTest(mySearchDao);
mySvc.setTxManagerForUnitTest(myTxManager); mySvc.setTransactionServiceForUnitTest(new NonTransactionalHapiTransactionService());
} }
@Test @Test
@ -48,7 +46,7 @@ public class DatabaseSearchCacheSvcImplTest {
when(mySearchDao.save(any())).thenReturn(updated); when(mySearchDao.save(any())).thenReturn(updated);
Search search = new Search(); Search search = new Search();
Optional<Search> outcome = mySvc.tryToMarkSearchAsInProgress(search); Optional<Search> outcome = mySvc.tryToMarkSearchAsInProgress(search, RequestPartitionId.allPartitions());
assertTrue(outcome.isPresent()); assertTrue(outcome.isPresent());
verify(mySearchDao, times(1)).save(any()); verify(mySearchDao, times(1)).save(any());
@ -63,7 +61,7 @@ public class DatabaseSearchCacheSvcImplTest {
when(mySearchDao.save(any())).thenThrow(new HibernateException("FOO")); when(mySearchDao.save(any())).thenThrow(new HibernateException("FOO"));
Search search = new Search(); Search search = new Search();
Optional<Search> outcome = mySvc.tryToMarkSearchAsInProgress(search); Optional<Search> outcome = mySvc.tryToMarkSearchAsInProgress(search, RequestPartitionId.allPartitions());
assertFalse(outcome.isPresent()); assertFalse(outcome.isPresent());
verify(mySearchDao, times(1)).save(any()); verify(mySearchDao, times(1)).save(any());
} }
@ -75,7 +73,7 @@ public class DatabaseSearchCacheSvcImplTest {
when(mySearchDao.findById(any())).thenReturn(Optional.of(updated)); when(mySearchDao.findById(any())).thenReturn(Optional.of(updated));
Search search = new Search(); Search search = new Search();
Optional<Search> outcome = mySvc.tryToMarkSearchAsInProgress(search); Optional<Search> outcome = mySvc.tryToMarkSearchAsInProgress(search, RequestPartitionId.allPartitions());
assertFalse(outcome.isPresent()); assertFalse(outcome.isPresent());
verify(mySearchDao, never()).save(any()); verify(mySearchDao, never()).save(any());
} }

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -7,7 +7,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
@ -44,6 +44,11 @@
<artifactId>httpcore</artifactId> <artifactId>httpcore</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
</dependency>
<!-- <!--
Spring is added as an optional dependency just so that it Spring is added as an optional dependency just so that it
can be used for CORS can be used for CORS

View File

@ -39,6 +39,7 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding; import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
import ca.uhn.fhir.rest.server.util.NarrativeUtil;
import ca.uhn.fhir.util.ClasspathUtil; import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
@ -800,28 +801,11 @@ public class ResponseHighlighterInterceptor {
} }
/* /*
* FHIR only allows a pretty restricted set of HTML tags and attributes, in order * Sanitize the narrative so that it's safe to render (strip any
* to avoid any risk of injection attacks. If anything that isn't explicitly allowed * links, potentially unsafe CSS, etc.)
* by FHIR is present in the narrative we won't render it and instead we'll explain
* what validation problems we found.
*/ */
if (xhtmlNode != null) { if (xhtmlNode != null) {
List<String> errors = new ArrayList<>(); xhtmlNode = NarrativeUtil.sanitize(xhtmlNode);
Validate.isTrue(xhtmlNode.getName() == null);
xhtmlNode.getFirstElement().validate(errors, "", true, false, false);
if (errors.size() > 0) {
StringBuilder errorNarrative = new StringBuilder();
errorNarrative.append("Can not render narrative due to validation errors:");
errorNarrative.append("<ul>");
errors.forEach(next -> {
errorNarrative.append("<li>");
errorNarrative.append(sanitizeUrlPart(next));
errorNarrative.append("</li>");
});
errorNarrative.append("</ul>");
return errorNarrative.toString();
}
return xhtmlNode.getValueAsString(); return xhtmlNode.getValueAsString();
} }

View File

@ -0,0 +1,91 @@
/*-
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.rest.server.util;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.owasp.html.HtmlPolicyBuilder;
import org.owasp.html.PolicyFactory;
import org.owasp.html.Sanitizers;
public class NarrativeUtil {
/**
* Non instantiable
*/
private NarrativeUtil() {
super();
}
/**
* This method accepts an Xhtml (generally a narrative) and sanitizes it,
* removing unsafe elements. This method leverages the
* <a href="https://github.com/OWASP/java-html-sanitizer/blob/master/pom.xml">OWASP Java HTML Sanitizer</a>
* to perform this task. The policy allows the following:
* <ul>
* <li>Block tags are allowed</li>
* <li>Tables are allowed</li>
* <li>Basic styles are allowed but any styles considered unsafe are removed from the document (e.g. any style declarations that could be used to load external content)</li>
* <li>Attributes considered safe are allowed</li>
* <li>Any links (&lta href="....") are removed although any text inside the link is retained</li>
* <li>All other elements and attributes are removed</li>
* </ul>
*/
public static String sanitize(String theHtml) {
XhtmlNode node = new XhtmlNode();
node.setValueAsString(theHtml);
return sanitize(node).getValueAsString();
}
/**
* This method accepts an Xhtml (generally a narrative) and sanitizes it,
* removing unsafe elements. This method leverages the
* <a href="https://github.com/OWASP/java-html-sanitizer/blob/master/pom.xml">OWASP Java HTML Sanitizer</a>
* to perform this task. The policy allows the following:
* <ul>
* <li>Block tags are allowed</li>
* <li>Tables are allowed</li>
* <li>Basic styles are allowed but any styles considered unsafe are removed from the document (e.g. any style declarations that could be used to load external content)</li>
* <li>Attributes considered safe are allowed</li>
* <li>Any links (&lta href="....") are removed although any text inside the link is retained</li>
* <li>All other elements and attributes are removed</li>
* </ul>
*/
public static XhtmlNode sanitize(XhtmlNode theNode) {
String html = theNode.getValueAsString();
PolicyFactory idPolicy = new HtmlPolicyBuilder()
.allowAttributes("id").globally()
.toFactory();
PolicyFactory policy = Sanitizers.FORMATTING
.and(Sanitizers.BLOCKS)
.and(Sanitizers.TABLES)
.and(Sanitizers.STYLES)
.and(idPolicy);
String safeHTML = policy.sanitize(html);
XhtmlNode retVal = new XhtmlNode();
retVal.setValueAsString(safeHTML);
return retVal;
}
}

View File

@ -0,0 +1,39 @@
package ca.uhn.fhir.rest.server.util;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class NarrativeUtilTest {
@ParameterizedTest
@CsvSource({
"<div><SPAN ID=\"foo\">hello</SPAN></div> , <div xmlns=\"http://www.w3.org/1999/xhtml\"><span id=\"foo\">hello</span></div>",
"<div><span id=\"foo\">hello</span></div> , <div xmlns=\"http://www.w3.org/1999/xhtml\"><span id=\"foo\">hello</span></div>",
"<div><SPAN ONCLICK=\"hello()\">hello</SPAN></div> , <div xmlns=\"http://www.w3.org/1999/xhtml\">hello</div>",
"<div><span onclick=\"hello()\">hello</span></div> , <div xmlns=\"http://www.w3.org/1999/xhtml\">hello</div>",
"<div><a href=\"http://goodbye\">hello</a></div> , <div xmlns=\"http://www.w3.org/1999/xhtml\">hello</div>",
"<div><table><tr><td>hello</td></tr></table></div> , <div xmlns=\"http://www.w3.org/1999/xhtml\"><table><tbody><tr><td>hello</td></tr></tbody></table></div>",
"<div><span style=\"font-size: 100px;\">hello</span></div> , <div xmlns=\"http://www.w3.org/1999/xhtml\"><span style=\"font-size:100px\">hello</span></div>",
"<div><span style=\"background: url('test.jpg')\">hello</span></div> , <div xmlns=\"http://www.w3.org/1999/xhtml\">hello</div>",
"hello , <div xmlns=\"http://www.w3.org/1999/xhtml\">hello</div>",
"empty , null",
"null , null"
})
public void testValidateIsCaseInsensitive(String theHtml, String theExpected) {
String output = NarrativeUtil.sanitize(fixNull(theHtml));
assertEquals(fixNull(theExpected), output);
}
private String fixNull(String theExpected) {
if ("null".equals(theExpected)) {
return null;
}
if ("empty".equals(theExpected)) {
return "";
}
return theExpected;
}
}

View File

@ -7,7 +7,8 @@
<parent> <parent>
<artifactId>hapi-fhir-serviceloaders</artifactId> <artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -7,7 +7,8 @@
<parent> <parent>
<artifactId>hapi-fhir-serviceloaders</artifactId> <artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
@ -20,7 +21,8 @@
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId> <artifactId>hapi-fhir-caching-api</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.ben-manes.caffeine</groupId> <groupId>com.github.ben-manes.caffeine</groupId>

View File

@ -7,7 +7,8 @@
<parent> <parent>
<artifactId>hapi-fhir-serviceloaders</artifactId> <artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -7,7 +7,8 @@
<parent> <parent>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId> <artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
</parent> </parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId> <artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId> <artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
</parent> </parent>
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId> <artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId> <artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
</parent> </parent>
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId> <artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId> <artifactId>hapi-fhir-spring-boot</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
</parent> </parent>
<artifactId>hapi-fhir-spring-boot-samples</artifactId> <artifactId>hapi-fhir-spring-boot-samples</artifactId>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -7,7 +7,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -7,7 +7,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -7,7 +7,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -43,6 +43,6 @@ public interface ISearchCoordinatorSvc<T extends IResourcePersistentId> {
* Fetch the total number of search results for the given currently executing search, if one is currently executing and * Fetch the total number of search results for the given currently executing search, if one is currently executing and
* the total is known. Will return empty otherwise * the total is known. Will return empty otherwise
*/ */
Optional<Integer> getSearchTotal(String theUuid, @Nullable RequestDetails theRequestDetails); Optional<Integer> getSearchTotal(String theUuid, @Nullable RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId);
} }

View File

@ -54,6 +54,22 @@ public interface IHapiTransactionService {
*/ */
IExecutionBuilder withSystemRequest(); IExecutionBuilder withSystemRequest();
/**
* Fluent builder for internal system requests with no external
* {@link RequestDetails} associated and a pre-specified partition ID.
* This method is sugar for
* <pre>
* withSystemRequest()
* .withRequestPartitionId(thePartitionId);
* </pre>
*
* @since 6.6.0
*/
default IExecutionBuilder withSystemRequestOnPartition(RequestPartitionId theRequestPartitionId) {
return withSystemRequest()
.withRequestPartitionId(theRequestPartitionId);
}
/** /**
* @deprecated It is highly recommended to use {@link #withRequest(RequestDetails)} instead of this method, for increased visibility. * @deprecated It is highly recommended to use {@link #withRequest(RequestDetails)} instead of this method, for increased visibility.
*/ */

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -990,8 +990,7 @@ public class ResponseHighlightingInterceptorTest {
HttpGet httpGet = new HttpGet(url); HttpGet httpGet = new HttpGet(url);
try (CloseableHttpResponse response = ourClient.execute(httpGet)) { try (CloseableHttpResponse response = ourClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8); String resp = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
assertThat(resp, not(containsString("<thead><tr><th>Header1</th><th>Header2</th></tr></thead>"))); assertThat(resp, containsString("<table><thead><tr><th>Header1</th>"));
assertThat(resp, containsString("Error at div/table: Found attribute table.onclick in a resource"));
} }
} }

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -11,6 +11,7 @@ import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse; import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.client.impl.GenericClient; import ca.uhn.fhir.rest.client.impl.GenericClient;
import ca.uhn.fhir.rest.server.util.NarrativeUtil;
import ca.uhn.fhir.to.model.HomeRequest; import ca.uhn.fhir.to.model.HomeRequest;
import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.ExtensionConstants; import ca.uhn.fhir.util.ExtensionConstants;
@ -522,7 +523,7 @@ public class BaseController {
theModelMap.put("resultBodyIsLong", resultBodyText.length() > 1000); theModelMap.put("resultBodyIsLong", resultBodyText.length() > 1000);
theModelMap.put("requestHeaders", requestHeaders); theModelMap.put("requestHeaders", requestHeaders);
theModelMap.put("responseHeaders", responseHeaders); theModelMap.put("responseHeaders", responseHeaders);
theModelMap.put("narrative", narrativeString); theModelMap.put("narrative", NarrativeUtil.sanitize(narrativeString));
theModelMap.put("latencyMs", theLatency); theModelMap.put("latencyMs", theLatency);
theModelMap.put("config", myConfig); theModelMap.put("config", myConfig);

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -7,7 +7,8 @@
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<name>HAPI-FHIR</name> <name>HAPI-FHIR</name>
<description>An open-source implementation of the FHIR specification in Java.</description> <description>An open-source implementation of the FHIR specification in Java.</description>
<url>https://hapifhir.io</url> <url>https://hapifhir.io</url>
@ -1059,6 +1060,11 @@
<artifactId>caffeine</artifactId> <artifactId>caffeine</artifactId>
<version>${caffeine_version}</version> <version>${caffeine_version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
<version>20211018.2</version>
</dependency>
<dependency> <dependency>
<groupId>com.graphql-java</groupId> <groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId> <artifactId>graphql-java</artifactId>

View File

@ -6,7 +6,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>

View File

@ -4,7 +4,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,8 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId> <artifactId>hapi-fhir</artifactId>
<version>6.5.10-SNAPSHOT</version> <version>6.5.11-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>