diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index bfddf6d68cc..50ba12ea791 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -155,6 +155,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { @Override public void cancelAllActiveSearches() { for (SearchTask next : myIdToSearchTask.values()) { + ourLog.info("Requesting immediate abort of search: {}", next.getSearch().getUuid()); next.requestImmediateAbort(); AsyncUtil.awaitLatchAndIgnoreInterrupt(next.getCompletionLatch(), 30, TimeUnit.SECONDS); } @@ -631,6 +632,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { Integer awaitInitialSync() { ourLog.trace("Awaiting initial sync"); do { + ourLog.trace("Search {} aborted: {}", getSearch().getUuid(), !isNotAborted()); if (AsyncUtil.awaitLatchAndThrowInternalErrorExceptionOnInterrupt(getInitialCollectionLatch(), 250L, TimeUnit.MILLISECONDS)) { break; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProviderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProviderTest.java index 74355e728fb..99b50b40616 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProviderTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProviderTest.java @@ -8,8 +8,10 @@ import ca.uhn.fhir.rest.client.apache.ResourceEntity; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.test.utilities.JettyUtil; +import ca.uhn.fhir.util.UrlUtil; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; +import org.apache.http.HttpRequestInterceptor; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; @@ -96,7 +98,7 @@ public class BulkDataExportProviderTest { } @Test - public void testSuccessfulInitiateBulkRequest() throws IOException { + public void testSuccessfulInitiateBulkRequest_Post() throws IOException { IBulkDataExportSvc.JobInfo jobInfo = new IBulkDataExportSvc.JobInfo() .setJobId(A_JOB_ID); @@ -110,9 +112,12 @@ public class BulkDataExportProviderTest { input.addParameter(JpaConstants.PARAM_EXPORT_SINCE, now); input.addParameter(JpaConstants.PARAM_EXPORT_TYPE_FILTER, new StringType("Patient?identifier=foo")); + ourLog.info(myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input)); + HttpPost post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT); post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC); post.setEntity(new ResourceEntity(myCtx, input)); + ourLog.info("Request: {}", post); try (CloseableHttpResponse response = myClient.execute(post)) { ourLog.info("Response: {}", response.toString()); @@ -129,6 +134,40 @@ public class BulkDataExportProviderTest { } + @Test + public void testSuccessfulInitiateBulkRequest_Get() throws IOException { + + IBulkDataExportSvc.JobInfo jobInfo = new IBulkDataExportSvc.JobInfo() + .setJobId(A_JOB_ID); + when(myBulkDataExportSvc.submitJob(any(), any(), any(), any())).thenReturn(jobInfo); + + InstantType now = InstantType.now(); + + String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT + + "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON) + + "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("Patient, Practitioner") + + "&" + JpaConstants.PARAM_EXPORT_SINCE+ "="+ UrlUtil.escapeUrlParam(now.getValueAsString()) + + "&" + JpaConstants.PARAM_EXPORT_TYPE_FILTER + "=" + UrlUtil.escapeUrlParam("Patient?identifier=foo"); + + HttpGet get = new HttpGet(url); + get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC); + ourLog.info("Request: {}", url); + try (CloseableHttpResponse response = myClient.execute(get)) { + ourLog.info("Response: {}", response.toString()); + + assertEquals(202, response.getStatusLine().getStatusCode()); + assertEquals("Accepted", response.getStatusLine().getReasonPhrase()); + assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue()); + } + + verify(myBulkDataExportSvc, times(1)).submitJob(myOutputFormatCaptor.capture(), myResourceTypesCaptor.capture(), mySinceCaptor.capture(), myFiltersCaptor.capture()); + assertEquals(Constants.CT_FHIR_NDJSON, myOutputFormatCaptor.getValue()); + assertThat(myResourceTypesCaptor.getValue(), containsInAnyOrder("Patient", "Practitioner")); + assertThat(mySinceCaptor.getValue(), notNullValue()); + assertThat(myFiltersCaptor.getValue(), containsInAnyOrder("Patient?identifier=foo")); + + } + @Test public void testPollForStatus_BUILDING() throws IOException { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java index 9f0435102e4..fcd0961bd9f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java @@ -33,6 +33,7 @@ import java.util.Date; import java.util.concurrent.atomic.AtomicLong; import static ca.uhn.fhir.jpa.search.cache.DatabaseSearchCacheSvcImpl.DEFAULT_CUTOFF_SLACK; +import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.*; @@ -165,13 +166,10 @@ public class FhirResourceDaoR4SearchPageExpiryTest extends BaseJpaR4Test { DatabaseSearchCacheSvcImpl.setNowForUnitTests(search3timestamp.get() + 2100); myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem(); - newTxTemplate().execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus theArg0) { - assertFalse("Search 1 still exists", mySearchEntityDao.findByUuidAndFetchIncludes(searchUuid1).isPresent()); - assertFalse("Search 3 still exists", mySearchEntityDao.findByUuidAndFetchIncludes(searchUuid3).isPresent()); - } - }); + + await().until(()-> newTxTemplate().execute(t -> !mySearchEntityDao.findByUuidAndFetchIncludes(searchUuid1).isPresent())); + await().until(()-> newTxTemplate().execute(t -> !mySearchEntityDao.findByUuidAndFetchIncludes(searchUuid3).isPresent())); + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java index 0313ae26286..33459ed691c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImplTest.java @@ -26,9 +26,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.slf4j.Logger; @@ -39,8 +37,12 @@ import org.springframework.transaction.TransactionStatus; import javax.persistence.EntityManager; import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast; +import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.*; @@ -108,9 +110,9 @@ public class SearchCoordinatorSvcImplTest { }).when(myCallingDao).injectDependenciesIntoBundleProvider(any(PersistedJpaBundleProvider.class)); } - private List createPidSequence(int from, int to) { + private List createPidSequence(int to) { List pids = new ArrayList<>(); - for (long i = from; i < to; i++) { + for (long i = 10; i < to; i++) { pids.add(i); } return pids; @@ -134,7 +136,7 @@ public class SearchCoordinatorSvcImplTest { SearchParameterMap params = new SearchParameterMap(); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(10, 800); + List pids = createPidSequence(800); IResultIterator iter = new FailAfterNIterator(new SlowIterator(pids.iterator(), 2), 300); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); @@ -166,7 +168,7 @@ public class SearchCoordinatorSvcImplTest { SearchParameterMap params = new SearchParameterMap(); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(10, 800); + List pids = createPidSequence(800); SlowIterator iter = new SlowIterator(pids.iterator(), 1); when(mySearchBuilder.createQuery(any(), any(), any())).thenReturn(iter); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); @@ -203,9 +205,7 @@ public class SearchCoordinatorSvcImplTest { myCurrentSearch = search; return search; }); - when(mySearchCacheSvc.fetchByUuid(any())).thenAnswer(t -> { - return Optional.ofNullable(myCurrentSearch); - }); + when(mySearchCacheSvc.fetchByUuid(any())).thenAnswer(t -> Optional.ofNullable(myCurrentSearch)); IFhirResourceDao dao = myCallingDao; when(myDaoRegistry.getResourceDao(any(String.class))).thenReturn(dao); @@ -274,7 +274,7 @@ public class SearchCoordinatorSvcImplTest { SearchParameterMap params = new SearchParameterMap(); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(10, 800); + List pids = createPidSequence(800); SlowIterator iter = new SlowIterator(pids.iterator(), 2); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); @@ -294,32 +294,43 @@ public class SearchCoordinatorSvcImplTest { } @Test - public void testCancelActiveSearches() { + public void testCancelActiveSearches() throws InterruptedException { SearchParameterMap params = new SearchParameterMap(); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(10, 400); - SlowIterator iter = new SlowIterator(pids.iterator(), 50); + List pids = createPidSequence(800); + SlowIterator iter = new SlowIterator(pids.iterator(), 500); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); - doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); - IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); assertNotNull(result.getUuid()); - assertEquals(null, result.size()); - List resources; - - resources = result.getResources(0, 1); + CountDownLatch completionLatch = new CountDownLatch(1); + Runnable taskStarter = () -> { + try { + ourLog.info("About to pull the first resource"); + List resources = result.getResources(0, 1); + ourLog.info("Done pulling the first resource"); assertEquals(1, resources.size()); + } finally { + completionLatch.countDown(); + } + }; + new Thread(taskStarter).start(); + await().until(()->iter.getCountReturned() >= 3); + + ourLog.info("About to cancel all searches"); mySvc.cancelAllActiveSearches(); + ourLog.info("Done cancelling all searches"); try { result.getResources(10, 20); } catch (InternalErrorException e) { assertEquals("Abort has been requested", e.getMessage()); } + + completionLatch.await(10, TimeUnit.SECONDS); } /** @@ -331,7 +342,7 @@ public class SearchCoordinatorSvcImplTest { SearchParameterMap params = new SearchParameterMap(); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(10, 800); + List pids = createPidSequence(800); IResultIterator iter = new SlowIterator(pids.iterator(), 2); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); when(mySearchCacheSvc.save(any())).thenAnswer(t -> t.getArguments()[0]); @@ -376,7 +387,7 @@ public class SearchCoordinatorSvcImplTest { SearchParameterMap params = new SearchParameterMap(); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(10, 100); + List pids = createPidSequence(100); SlowIterator iter = new SlowIterator(pids.iterator(), 2); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(iter); @@ -384,7 +395,7 @@ public class SearchCoordinatorSvcImplTest { IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); assertNotNull(result.getUuid()); - assertEquals(90, result.size().intValue()); + assertEquals(90, Objects.requireNonNull(result.size()).intValue()); List resources = result.getResources(0, 30); assertEquals(30, resources.size()); @@ -396,6 +407,7 @@ public class SearchCoordinatorSvcImplTest { @Test public void testGetPage() { Pageable page = SearchCoordinatorSvcImpl.toPage(50, 73); + assert page != null; assertEquals(50, page.getOffset()); assertEquals(23, page.getPageSize()); } @@ -458,14 +470,14 @@ public class SearchCoordinatorSvcImplTest { params.setLoadSynchronous(true); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(10, 800); + List pids = createPidSequence(800); when(mySearchBuilder.createQuery(same(params), any(), any())).thenReturn(new ResultIterator(pids.iterator())); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); assertNull(result.getUuid()); - assertEquals(790, result.size().intValue()); + assertEquals(790, Objects.requireNonNull(result.size()).intValue()); List resources = result.getResources(0, 10000); assertEquals(790, resources.size()); @@ -479,15 +491,15 @@ public class SearchCoordinatorSvcImplTest { params.setLoadSynchronousUpTo(100); params.add("name", new StringParam("ANAME")); - List pids = createPidSequence(10, 800); + List pids = createPidSequence(800); when(mySearchBuilder.createQuery(same(params), any(), nullable(RequestDetails.class))).thenReturn(new ResultIterator(pids.iterator())); - pids = createPidSequence(10, 110); + pids = createPidSequence(110); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(eq(pids), any(Collection.class), any(List.class), anyBoolean(), nullable(RequestDetails.class)); IBundleProvider result = mySvc.registerSearch(myCallingDao, params, "Patient", new CacheControlDirective(), null); assertNull(result.getUuid()); - assertEquals(100, result.size().intValue()); + assertEquals(100, Objects.requireNonNull(result.size()).intValue()); List resources = result.getResources(0, 10000); assertEquals(100, resources.size()); @@ -628,6 +640,7 @@ public class SearchCoordinatorSvcImplTest { private int myDelay; private Iterator myWrap; private List myReturnedValues = new ArrayList<>(); + private AtomicInteger myCountReturned = new AtomicInteger(0); SlowIterator(Iterator theWrap, int theDelay) { myWrap = theWrap; @@ -648,6 +661,10 @@ public class SearchCoordinatorSvcImplTest { return retVal; } + public int getCountReturned() { + return myCountReturned.get(); + } + @Override public Long next() { try { @@ -657,6 +674,7 @@ public class SearchCoordinatorSvcImplTest { } Long retVal = myWrap.next(); myReturnedValues.add(retVal); + myCountReturned.incrementAndGet(); return retVal; } diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/HashMapResourceProviderRule.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/HashMapResourceProviderRule.java index 0dcc89b57e1..dd73d69245b 100644 --- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/HashMapResourceProviderRule.java +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/HashMapResourceProviderRule.java @@ -1,6 +1,5 @@ package ca.uhn.fhir.test.utilities.server; -import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider; import org.hl7.fhir.instance.model.api.IBaseResource; import org.junit.rules.TestRule; @@ -14,7 +13,6 @@ public class HashMapResourceProviderRule extends HashMa /** * Constructor * - * @param theFhirContext The FHIR context * @param theResourceType The resource type to support */ public HashMapResourceProviderRule(RestfulServerRule theRestfulServerRule, Class theResourceType) { diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-navbar-top.html b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-navbar-top.html index 7814bf82079..b91d4e111b0 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-navbar-top.html +++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-navbar-top.html @@ -43,8 +43,8 @@