diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 9161e8419d2..4af1b5ee0f8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.interceptor.api.HookParams; import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java index 447f45fffa0..739122096fe 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java @@ -23,7 +23,9 @@ package ca.uhn.fhir.jpa.dao.index; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.model.entity.ForcedId; -import ca.uhn.fhir.model.dstu2.resource.Specimen; +import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.model.search.PerformanceMessage; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import com.google.common.collect.ListMultimap; @@ -43,6 +45,8 @@ public class IdHelperService { protected IForcedIdDao myForcedIdDao; @Autowired(required = true) private DaoConfig myDaoConfig; + @Autowired + private IInterceptorBroadcaster myInterceptorBroadcaster; public void delete(ForcedId forcedId) { myForcedIdDao.delete(forcedId); @@ -51,7 +55,7 @@ public class IdHelperService { public Long translateForcedIdToPid(String theResourceName, String theResourceId) throws ResourceNotFoundException { // We only pass 1 input in so only 0..1 will come back IdDt id = new IdDt(theResourceName, theResourceId); - List matches = translateForcedIdToPids(myDaoConfig, myForcedIdDao, Collections.singletonList(id)); + List matches = translateForcedIdToPids(myDaoConfig, myInterceptorBroadcaster, myForcedIdDao, Collections.singletonList(id)); assert matches.size() <= 1; if (matches.isEmpty()) { throw new ResourceNotFoundException(id); @@ -60,10 +64,10 @@ public class IdHelperService { } public List translateForcedIdToPids(Collection theId) { - return IdHelperService.translateForcedIdToPids(myDaoConfig, myForcedIdDao, theId); + return IdHelperService.translateForcedIdToPids(myDaoConfig, myInterceptorBroadcaster, myForcedIdDao, theId); } - static List translateForcedIdToPids(DaoConfig theDaoConfig, IForcedIdDao theForcedIdDao, Collection theId) { + static List translateForcedIdToPids(DaoConfig theDaoConfig, IInterceptorBroadcaster theInterceptorBroadcaster, IForcedIdDao theForcedIdDao, Collection theId) { theId.forEach(id -> Validate.isTrue(id.hasIdPart())); if (theId.isEmpty()) { @@ -89,7 +93,13 @@ public class IdHelperService { String nextResourceType = nextEntry.getKey(); Collection nextIds = nextEntry.getValue(); if (isBlank(nextResourceType)) { + + PerformanceMessage msg = new PerformanceMessage() + .setMessage("This search uses unqualified resource IDs (an ID without a resource type). This is less efficient than using a qualified type."); + theInterceptorBroadcaster.callHooks(Pointcut.PERFTRACE_MESSAGE, msg); + retVal.addAll(theForcedIdDao.findByForcedId(nextIds)); + } else { retVal.addAll(theForcedIdDao.findByTypeAndForcedId(nextResourceType, nextIds)); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/Search.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/Search.java index a9b0408baa2..0c85f1d44e4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/Search.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/Search.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.entity; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.rest.param.DateRangeParam; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/PerformanceTracingLoggingInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/PerformanceTracingLoggingInterceptor.java index 3711a2c5a36..f75162f690f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/PerformanceTracingLoggingInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/PerformanceTracingLoggingInterceptor.java @@ -7,24 +7,25 @@ import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.util.LogUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; @Interceptor(manualRegistration = true) public class PerformanceTracingLoggingInterceptor { private static final Logger ourLog = LoggerFactory.getLogger(PerformanceTracingLoggingInterceptor.class); private final Logger myLog; - private final LogUtil.Level myLevel; + private final Level myLevel; /** * Constructor that logs to this class with a level of INFO */ public PerformanceTracingLoggingInterceptor() { - this(ourLog, LogUtil.Level.INFO); + this(ourLog, Level.INFO); } /** * Constructor that logs to a custom logger and level */ - public PerformanceTracingLoggingInterceptor(Logger theLog, LogUtil.Level theLevel) { + public PerformanceTracingLoggingInterceptor(Logger theLog, Level theLevel) { myLog = theLog; myLevel = theLevel; } @@ -39,6 +40,21 @@ public class PerformanceTracingLoggingInterceptor { log("Query found {} matches in {} for query {}", theOutcome.getFoundMatchesCount(), theOutcome.getQueryStopwatch(), theOutcome.getSearchUuid()); } + @Hook(value = Pointcut.PERFTRACE_SEARCH_COMPLETE) + public void searchComplete(SearchRuntimeDetails theOutcome) { + log("Query {} is complete in {} - Found {} matches", theOutcome.getSearchUuid(), theOutcome.getQueryStopwatch(), theOutcome.getFoundMatchesCount()); + } + + @Hook(value = Pointcut.PERFTRACE_SEARCH_PASS_COMPLETE) + public void searchPassComplete(SearchRuntimeDetails theOutcome) { + log("Query {} pass complete and set to status {} in {} - Found {} matches", theOutcome.getSearchUuid(), theOutcome.getSearchStatus(), theOutcome.getQueryStopwatch(), theOutcome.getFoundMatchesCount()); + } + + @Hook(value = Pointcut.PERFTRACE_SEARCH_FAILED) + public void searchFailed(SearchRuntimeDetails theOutcome) { + log("Query {} failed in {} - Found {} matches", theOutcome.getSearchUuid(), theOutcome.getQueryStopwatch(), theOutcome.getFoundMatchesCount()); + } + private void log(String theMessage, Object... theArgs) { LogUtil.log(myLog, myLevel, theMessage, theArgs); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java index 0afd31f2b16..a8d7a668f9b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java @@ -26,16 +26,13 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import ca.uhn.fhir.jpa.dao.IDao; import ca.uhn.fhir.jpa.dao.ISearchBuilder; import ca.uhn.fhir.jpa.entity.Search; -import ca.uhn.fhir.jpa.entity.SearchStatusEnum; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl.SearchTask; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundleProvider { private static final Logger ourLog = LoggerFactory.getLogger(PersistedJpaSearchFirstPageBundleProvider.class); 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 6dbf994f7e0..68f375582d2 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 @@ -26,6 +26,9 @@ import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; +import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.Include; @@ -70,6 +73,7 @@ import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.persistence.EntityManager; import java.util.*; +import java.util.List; import java.util.concurrent.*; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; @@ -97,6 +101,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { @Autowired private ISearchResultDao mySearchResultDao; @Autowired + private IInterceptorBroadcaster myInterceptorBroadcaster; + @Autowired private PlatformTransactionManager myManagedTxManager; @Autowired private DaoRegistry myDaoRegistry; @@ -704,7 +710,13 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { } }); -// FIXME: JA interceptor + mySearchRuntimeDetails.setSearchStatus(mySearch.getStatus()); + if (mySearch.getStatus() == SearchStatusEnum.FINISHED) { + myInterceptorBroadcaster.callHooks(Pointcut.PERFTRACE_SEARCH_COMPLETE, mySearchRuntimeDetails); + } else { + myInterceptorBroadcaster.callHooks(Pointcut.PERFTRACE_SEARCH_PASS_COMPLETE, mySearchRuntimeDetails); + } + ourLog.info("Have completed search for [{}{}] and found {} resources in {}ms - Status is {}", mySearch.getResourceType(), mySearch.getSearchQueryString(), mySyncedPids.size(), sw.getMillis(), mySearch.getStatus()); } catch (Throwable t) { @@ -742,6 +754,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { mySearch.setFailureCode(failureCode); mySearch.setStatus(SearchStatusEnum.FAILED); + mySearchRuntimeDetails.setSearchStatus(mySearch.getStatus()); + myInterceptorBroadcaster.callHooks(Pointcut.PERFTRACE_SEARCH_FAILED, mySearchRuntimeDetails); + saveSearch(); } finally { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionInterceptorLoader.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionInterceptorLoader.java index 5af1faf0591..86bb3be4d14 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionInterceptorLoader.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionInterceptorLoader.java @@ -57,12 +57,12 @@ public class SubscriptionInterceptorLoader { if (!supportedSubscriptionTypes.isEmpty()) { loadSubscriptions(); ourLog.info("Registering subscription activating interceptor"); - myInterceptorRegistry.registerInterceptor(mySubscriptionActivatingInterceptor); + myInterceptorRegistry.registerAnonymousInterceptor(mySubscriptionActivatingInterceptor); } if (myDaoConfig.isSubscriptionMatchingEnabled()) { mySubscriptionMatcherInterceptor.start(); ourLog.info("Registering subscription matcher interceptor"); - myInterceptorRegistry.registerInterceptor(mySubscriptionMatcherInterceptor); + myInterceptorRegistry.registerAnonymousInterceptor(mySubscriptionMatcherInterceptor); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java index 89e2d1998bf..cb81f0d3af5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java @@ -142,7 +142,7 @@ public abstract class BaseJpaTest { protected CountDownLatch registerLatchHookInterceptor(int theCount, Pointcut theLatchPointcut) { CountDownLatch deliveryLatch = new CountDownLatch(theCount); - myInterceptorRegistry.registerAnonymousHookForUnitTest(theLatchPointcut, Integer.MAX_VALUE, t -> deliveryLatch.countDown()); + myInterceptorRegistry.registerAnonymousInterceptor(theLatchPointcut, Integer.MAX_VALUE, (thePointcut, t) -> deliveryLatch.countDown()); return deliveryLatch; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java index f6bf6eced40..ea055cc50db 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java @@ -323,7 +323,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { myDaoConfig.setInterceptors(myInterceptor); myPerformanceTracingLoggingInterceptor = new PerformanceTracingLoggingInterceptor(); - myInterceptorRegistry.registerInterceptor(myPerformanceTracingLoggingInterceptor); + myInterceptorRegistry.registerAnonymousInterceptor(myPerformanceTracingLoggingInterceptor); } @Before diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java index 0b497388698..119c07a00bd 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java @@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.entity.Search; -import ca.uhn.fhir.jpa.entity.SearchStatusEnum; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 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 1dd6c8a479d..17b3bf93c4a 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 @@ -4,7 +4,7 @@ import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.entity.Search; -import ca.uhn.fhir.jpa.entity.SearchStatusEnum; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.util.StopWatch; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java index 47c1164da76..68c3e70202c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java @@ -5,7 +5,7 @@ import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.entity.Search; -import ca.uhn.fhir.jpa.entity.SearchStatusEnum; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CompositionDocumentR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CompositionDocumentR4Test.java index 0efacbe93a6..a2bbbc981c0 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CompositionDocumentR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/CompositionDocumentR4Test.java @@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.model.interceptor.api.HookParams; -import ca.uhn.fhir.jpa.model.interceptor.api.IAnonymousLambdaHook; +import ca.uhn.fhir.jpa.model.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.EncodingEnum; @@ -32,6 +32,7 @@ import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItems; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -137,13 +138,13 @@ public class CompositionDocumentR4Test extends BaseResourceProviderR4Test { @Test public void testInterceptorHookIsCalledForAllContents_RESOURCE_MAY_BE_RETURNED() throws IOException { - IAnonymousLambdaHook pointcut = mock(IAnonymousLambdaHook.class); - myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.RESOURCE_MAY_BE_RETURNED, pointcut); + IAnonymousInterceptor pointcut = mock(IAnonymousInterceptor.class); + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.RESOURCE_MAY_BE_RETURNED, pointcut); String theUrl = ourServerBase + "/" + compId + "/$document?_format=json"; fetchBundle(theUrl, EncodingEnum.JSON); - Mockito.verify(pointcut, times(10)).invoke(myHookParamsCaptor.capture()); + Mockito.verify(pointcut, times(10)).invoke(eq(Pointcut.RESOURCE_MAY_BE_RETURNED), myHookParamsCaptor.capture()); List returnedClasses = myHookParamsCaptor .getAllValues() diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/HookInterceptorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/HookInterceptorR4Test.java index 74c9f355e40..67db89bd729 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/HookInterceptorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/HookInterceptorR4Test.java @@ -17,7 +17,7 @@ public class HookInterceptorR4Test extends BaseResourceProviderR4Test { @Test public void testOP_PRESTORAGE_RESOURCE_CREATED_ModifyResource() { - myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.OP_PRESTORAGE_RESOURCE_CREATED, t->{ + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.OP_PRESTORAGE_RESOURCE_CREATED, (thePointcut, t)->{ Patient contents = (Patient) t.get(IBaseResource.class, 0); contents.getNameFirstRep().setFamily("NEWFAMILY"); }); @@ -36,7 +36,7 @@ public class HookInterceptorR4Test extends BaseResourceProviderR4Test { @Test public void testOP_PRECOMMIT_RESOURCE_CREATED_ModifyResource() { - myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED, t->{ + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED, (thePointcut, t)->{ Patient contents = (Patient) t.get(IBaseResource.class, 0); contents.getNameFirstRep().setFamily("NEWFAMILY"); }); @@ -59,7 +59,7 @@ public class HookInterceptorR4Test extends BaseResourceProviderR4Test { p.setActive(true); IIdType id = ourClient.create().resource(p).execute().getId(); - myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.OP_PRESTORAGE_RESOURCE_UPDATED, t->{ + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.OP_PRESTORAGE_RESOURCE_UPDATED, (thePointcut, t)->{ Patient contents = (Patient) t.get(IBaseResource.class, 1); contents.getNameFirstRep().setFamily("NEWFAMILY"); }); @@ -83,7 +83,7 @@ public class HookInterceptorR4Test extends BaseResourceProviderR4Test { p.setActive(true); IIdType id = ourClient.create().resource(p).execute().getId(); - myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.OP_PRECOMMIT_RESOURCE_UPDATED, t->{ + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.OP_PRECOMMIT_RESOURCE_UPDATED, (thePointcut, t)->{ Patient contents = (Patient) t.get(IBaseResource.class, 1); contents.getNameFirstRep().setFamily("NEWFAMILY"); }); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderInterceptorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderInterceptorR4Test.java index 138a175e572..3fe539a7f89 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderInterceptorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderInterceptorR4Test.java @@ -1,5 +1,11 @@ package ca.uhn.fhir.jpa.provider.r4; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.model.interceptor.api.HookParams; +import ca.uhn.fhir.jpa.model.interceptor.api.IAnonymousInterceptor; +import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; @@ -13,6 +19,7 @@ import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter; import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.TestUtil; +import com.google.common.collect.Lists; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; @@ -29,14 +36,19 @@ import org.junit.After; import org.junit.AfterClass; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Collectors; +import static org.hamcrest.Matchers.in; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; @@ -48,6 +60,7 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes private IServerOperationInterceptor myDaoInterceptor; private IServerOperationInterceptor myServerInterceptor; + private List myInterceptors = new ArrayList<>(); @Override @After @@ -55,7 +68,11 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes super.after(); myDaoConfig.getInterceptors().remove(myDaoInterceptor); + myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds()); ourRestServer.unregisterInterceptor(myServerInterceptor); + + myInterceptorRegistry.unregisterInterceptors(myInterceptors); + myInterceptors.clear(); } @Override @@ -90,6 +107,51 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); } + @Mock + private IAnonymousInterceptor myHook; + + @Captor + private ArgumentCaptor myParamsCaptor; + + @Test + public void testPerfInterceptors() { + myDaoConfig.setSearchPreFetchThresholds(Lists.newArrayList(15, 100)); + for (int i = 0; i < 30; i++) { + Patient p = new Patient(); + p.addName().setFamily("FAM"+i); + myPatientDao.create(p); + } + + IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class); + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.PERFTRACE_SEARCH_FIRST_RESULT_LOADED, interceptor); + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.PERFTRACE_SEARCH_COMPLETE, interceptor); + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.PERFTRACE_SEARCH_FAILED, interceptor); + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.PERFTRACE_SEARCH_PASS_COMPLETE, interceptor); + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.PERFTRACE_SEARCH_SELECT_COMPLETE, interceptor); + myInterceptors.add(interceptor); + + Bundle results = ourClient.search().forResource(Patient.class).returnBundle(Bundle.class).execute(); + verify(interceptor, times(1)).invoke(eq(Pointcut.PERFTRACE_SEARCH_FIRST_RESULT_LOADED), myParamsCaptor.capture()); + verify(interceptor, times(1)).invoke(eq(Pointcut.PERFTRACE_SEARCH_SELECT_COMPLETE), myParamsCaptor.capture()); + verify(interceptor, times(0)).invoke(eq(Pointcut.PERFTRACE_SEARCH_COMPLETE), myParamsCaptor.capture()); + verify(interceptor, times(1)).invoke(eq(Pointcut.PERFTRACE_SEARCH_PASS_COMPLETE), myParamsCaptor.capture()); + verify(interceptor, times(0)).invoke(eq(Pointcut.PERFTRACE_SEARCH_FAILED), myParamsCaptor.capture()); + + SearchRuntimeDetails details = myParamsCaptor.getAllValues().get(0).get(SearchRuntimeDetails.class); + assertEquals(SearchStatusEnum.PASSCMPLET, details.getSearchStatus()); + + // Load the next (and final) page + reset(interceptor); + results = ourClient.loadPage().next(results).execute(); + verify(interceptor, times(1)).invoke(eq(Pointcut.PERFTRACE_SEARCH_FIRST_RESULT_LOADED), myParamsCaptor.capture()); + verify(interceptor, times(1)).invoke(eq(Pointcut.PERFTRACE_SEARCH_SELECT_COMPLETE), myParamsCaptor.capture()); + verify(interceptor, times(1)).invoke(eq(Pointcut.PERFTRACE_SEARCH_COMPLETE), myParamsCaptor.capture()); + verify(interceptor, times(0)).invoke(eq(Pointcut.PERFTRACE_SEARCH_PASS_COMPLETE), myParamsCaptor.capture()); + verify(interceptor, times(0)).invoke(eq(Pointcut.PERFTRACE_SEARCH_FAILED), myParamsCaptor.capture()); + + } + + @Test public void testCreateConditionalNoOpResourceInTransaction() throws Exception { String methodName = "foo"; @@ -154,15 +216,12 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes HttpPost post = new HttpPost(ourServerBase + "/Patient"); post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); - CloseableHttpResponse response = ourHttpClient.execute(post); - try { + try (CloseableHttpResponse response = ourHttpClient.execute(post)) { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info("Response was: {}", resp); assertEquals(201, response.getStatusLine().getStatusCode()); String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); - } finally { - response.close(); } ArgumentCaptor ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); @@ -235,7 +294,7 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes } @Test - public void testCreateReflexResourceTheHardWay() throws IOException, ServletException { + public void testCreateReflexResourceTheHardWay() { ServerOperationInterceptorAdapter interceptor = new ReflexInterceptor(); ourRestServer.registerInterceptor(interceptor); @@ -260,7 +319,7 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes } - @Test + @Test public void testCreateResourceWithVersionedReference() throws IOException, ServletException { String methodName = "testCreateResourceWithVersionedReference"; @@ -287,15 +346,12 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes HttpPost post = new HttpPost(ourServerBase + "/Patient"); post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); - CloseableHttpResponse response = ourHttpClient.execute(post); - try { + try (CloseableHttpResponse response = ourHttpClient.execute(post)) { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info("Response was: {}", resp); assertEquals(201, response.getStatusLine().getStatusCode()); String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); - } finally { - response.close(); } ArgumentCaptor ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); @@ -371,11 +427,8 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes String resource = myFhirCtx.newXmlParser().encodeResourceToString(theBundle); HttpPost post = new HttpPost(ourServerBase + "/"); post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); - CloseableHttpResponse response = ourHttpClient.execute(post); - try { + try (CloseableHttpResponse response = ourHttpClient.execute(post)) { assertEquals(200, response.getStatusLine().getStatusCode()); - } finally { - response.close(); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java index 80d06765fa5..3a2441bb556 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/StaleSearchDeletingSvcR4Test.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; import ca.uhn.fhir.rest.gclient.IClientExecutable; import ca.uhn.fhir.rest.gclient.IQuery; 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 e5ad4b68351..c2654ae8955 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 @@ -7,7 +7,7 @@ import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.SearchResult; -import ca.uhn.fhir.jpa.entity.SearchStatusEnum; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.entity.SearchTypeEnum; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.util.BaseIterator; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java index 4d43488172a..01b7c14fb66 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java @@ -82,7 +82,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test { @Before public void beforeRegisterRestHookListener() { mySubscriptionTestUtil.registerRestHookInterceptor(); - myInterceptorRegistry.registerInterceptor(ourSubscriptionDebugLogInterceptor); + myInterceptorRegistry.registerAnonymousInterceptor(ourSubscriptionDebugLogInterceptor); } @Before diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithInterceptorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithInterceptorR4Test.java index 39b249864a8..e47a9057c53 100755 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithInterceptorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithInterceptorR4Test.java @@ -132,7 +132,7 @@ public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test { @Test public void testAttributesAreCopiedAlongPipeline() throws Exception { AttributeCarryingInterceptor interceptor = new AttributeCarryingInterceptor(); - myInterceptorRegistry.registerInterceptor(interceptor); + myInterceptorRegistry.registerAnonymousInterceptor(interceptor); try { // Create a subscription @@ -191,7 +191,7 @@ public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test { registerLatch.await(10, TimeUnit.SECONDS); CountDownLatch latch = new CountDownLatch(1); - myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.SUBSCRIPTION_AFTER_DELIVERY_FAILED, params -> { + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.SUBSCRIPTION_AFTER_DELIVERY_FAILED, (thePointcut, params) -> { latch.countDown(); }); @@ -222,9 +222,9 @@ public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test { }).when(loggerMock).debug(any(), ArgumentMatchers.any()); SubscriptionDebugLogInterceptor interceptor = new SubscriptionDebugLogInterceptor(); - myInterceptorRegistry.registerInterceptor(interceptor); + myInterceptorRegistry.registerAnonymousInterceptor(interceptor); SubscriptionDebugLogInterceptor interceptor2 = new SubscriptionDebugLogInterceptor(t -> loggerMock, Level.DEBUG); - myInterceptorRegistry.registerInterceptor(interceptor2); + myInterceptorRegistry.registerAnonymousInterceptor(interceptor2); try { String payload = "application/json"; @@ -310,7 +310,7 @@ public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test { @Bean public MyTestInterceptor interceptor() { MyTestInterceptor retVal = new MyTestInterceptor(); - myInterceptorRegistry.registerInterceptor(retVal); + myInterceptorRegistry.registerAnonymousInterceptor(retVal); return retVal; } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/HookParams.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/HookParams.java index fee4ba6b206..81b50c7415a 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/HookParams.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/HookParams.java @@ -74,7 +74,11 @@ public class HookParams { return this; } - @SuppressWarnings("unchecked") + public T get(Class theParamType) { + return get(theParamType, 0); + } + + @SuppressWarnings("unchecked") public T get(Class theParamType, int theIndex) { List objects = myParams.get(theParamType); Object retVal = null; diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/IAnonymousLambdaHook.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/IAnonymousInterceptor.java similarity index 90% rename from hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/IAnonymousLambdaHook.java rename to hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/IAnonymousInterceptor.java index 5ac153fe0ab..616115d5e7b 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/IAnonymousLambdaHook.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/IAnonymousInterceptor.java @@ -29,8 +29,8 @@ import com.google.common.annotations.VisibleForTesting; */ @FunctionalInterface @VisibleForTesting -public interface IAnonymousLambdaHook { +public interface IAnonymousInterceptor { - void invoke(HookParams theArgs); + void invoke(Pointcut thePointcut, HookParams theArgs); } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/IInterceptorRegistry.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/IInterceptorRegistry.java index 2c0eeb1f65d..bacab372453 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/IInterceptorRegistry.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/IInterceptorRegistry.java @@ -22,6 +22,9 @@ package ca.uhn.fhir.jpa.model.interceptor.api; import com.google.common.annotations.VisibleForTesting; +import javax.annotation.Nullable; +import java.util.Collection; + public interface IInterceptorRegistry { int DEFAULT_ORDER = 0; @@ -56,7 +59,7 @@ public interface IInterceptorRegistry { * @param theInterceptor The interceptor to register * @return Returns true if at least one valid hook method was found on this interceptor */ - boolean registerInterceptor(Object theInterceptor); + boolean registerAnonymousInterceptor(Object theInterceptor); /** * Unregister an interceptor. This method has no effect if the given interceptor is not already registered. @@ -77,12 +80,12 @@ public interface IInterceptorRegistry { @Deprecated void unregisterGlobalInterceptor(Object theInterceptor); - @VisibleForTesting - void registerAnonymousHookForUnitTest(Pointcut thePointcut, IAnonymousLambdaHook theHook); + void registerAnonymousInterceptor(Pointcut thePointcut, IAnonymousInterceptor theInterceptor); - @VisibleForTesting - void registerAnonymousHookForUnitTest(Pointcut thePointcut, int theOrder, IAnonymousLambdaHook theHook); + void registerAnonymousInterceptor(Pointcut thePointcut, int theOrder, IAnonymousInterceptor theInterceptor); @VisibleForTesting void clearAnonymousHookForUnitTest(); + + void unregisterInterceptors(@Nullable Collection theInterceptors); } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/Pointcut.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/Pointcut.java index 74025e7f711..0a15f08d5e4 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/Pointcut.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/api/Pointcut.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.model.interceptor.api; * #L% */ +import ca.uhn.fhir.jpa.model.search.PerformanceMessage; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import java.util.Arrays; @@ -406,8 +407,93 @@ public enum Pointcut { * Hooks should return void. *

*/ - PERFTRACE_SEARCH_SELECT_COMPLETE(SearchRuntimeDetails.class.getName()); + PERFTRACE_SEARCH_SELECT_COMPLETE(SearchRuntimeDetails.class.getName()), + /** + * Note that this is a performance tracing hook. Use with caution in production + * systems, since calling it may (or may not) carry a cost. + *

+ * This hook is invoked when a search has failed for any reason. When this pointcut + * is invoked, the search has completed unsuccessfully and will not be continued. + *

+ * Hooks may accept the following parameters: + *
    + *
  • + * ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails - Contains details about the search being + * performed. Hooks should not modify this object. + *
  • + *
+ *

+ * Hooks should return void. + *

+ */ + PERFTRACE_SEARCH_FAILED(SearchRuntimeDetails.class.getName()), + + /** + * Note that this is a performance tracing hook. Use with caution in production + * systems, since calling it may (or may not) carry a cost. + *

+ * This hook is invoked when a search has failed for any reason. When this pointcut + * is invoked, a pass in the Search Coordinator has completed successfully, but + * not all possible resources have been loaded yet so a future paging request + * may trigger a new task that will load further resources. + *

+ * Hooks may accept the following parameters: + *
    + *
  • + * ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails - Contains details about the search being + * performed. Hooks should not modify this object. + *
  • + *
+ *

+ * Hooks should return void. + *

+ */ + PERFTRACE_SEARCH_PASS_COMPLETE(SearchRuntimeDetails.class.getName()), + + /** + * Note that this is a performance tracing hook. Use with caution in production + * systems, since calling it may (or may not) carry a cost. + *

+ * This hook is invoked when a search has failed for any reason. When this pointcut + * is invoked, a pass in the Search Coordinator has completed successfully, and all + * possible results have been fetched and loaded into the query cache. + *

+ * Hooks may accept the following parameters: + *
    + *
  • + * ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails - Contains details about the search being + * performed. Hooks should not modify this object. + *
  • + *
+ *

+ * Hooks should return void. + *

+ */ + PERFTRACE_SEARCH_COMPLETE(SearchRuntimeDetails.class.getName()), + + + /** + * Note that this is a performance tracing hook. Use with caution in production + * systems, since calling it may (or may not) carry a cost. + *

+ * This hook is invoked when any informational or warning messages generated by the + * SearchCoordinator are created. It is typically used to provide logging + * or capture details related to a specific request. + *

+ * Hooks may accept the following parameters: + *
    + *
  • + * {@link PerformanceMessage} - Contains the message + *
  • + *
+ *

+ * Hooks should return void. + *

+ */ + PERFTRACE_MESSAGE(PerformanceMessage.class.getName()) + + ; private final List myParameterTypes; Pointcut(String... theParameterTypes) { diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/executor/InterceptorService.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/executor/InterceptorService.java index 7fbb7753bac..b5524fc5eb5 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/executor/InterceptorService.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/interceptor/executor/InterceptorService.java @@ -32,6 +32,7 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; @@ -43,22 +44,37 @@ public class InterceptorService implements IInterceptorRegistry, IInterceptorBro private final ListMultimap myGlobalInvokers = ArrayListMultimap.create(); private final ListMultimap myAnonymousInvokers = ArrayListMultimap.create(); private final Object myRegistryMutex = new Object(); - private String myName; private final ThreadLocal> myThreadlocalInvokers = new ThreadLocal<>(); - private boolean myThreadlocalInvokersEnabled; + private String myName; + private boolean myThreadlocalInvokersEnabled = true; + + /** + * Constructor which uses a default name of "default" + */ + public InterceptorService() { + this("default"); + } /** * Constructor + * + * @param theName The name for this registry (useful for troubleshooting) */ public InterceptorService(String theName) { super(); myName = theName; } + /** + * Are threadlocal interceptors enabled on this registry (defaults to true) + */ public boolean isThreadlocalInvokersEnabled() { return myThreadlocalInvokersEnabled; } + /** + * Are threadlocal interceptors enabled on this registry (defaults to true) + */ public void setThreadlocalInvokersEnabled(boolean theThreadlocalInvokersEnabled) { myThreadlocalInvokersEnabled = theThreadlocalInvokersEnabled; } @@ -71,8 +87,8 @@ public class InterceptorService implements IInterceptorRegistry, IInterceptorBro @Override @VisibleForTesting - public void registerAnonymousHookForUnitTest(Pointcut thePointcut, IAnonymousLambdaHook theHook) { - registerAnonymousHookForUnitTest(thePointcut, DEFAULT_ORDER, theHook); + public void registerAnonymousInterceptor(Pointcut thePointcut, IAnonymousInterceptor theInterceptor) { + registerAnonymousInterceptor(thePointcut, DEFAULT_ORDER, theInterceptor); } public void setName(String theName) { @@ -80,17 +96,27 @@ public class InterceptorService implements IInterceptorRegistry, IInterceptorBro } @Override - public void registerAnonymousHookForUnitTest(Pointcut thePointcut, int theOrder, IAnonymousLambdaHook theHook) { + public void registerAnonymousInterceptor(Pointcut thePointcut, int theOrder, IAnonymousInterceptor theInterceptor) { Validate.notNull(thePointcut); - Validate.notNull(theHook); - - myAnonymousInvokers.put(thePointcut, new AnonymousLambdaInvoker(theHook, theOrder)); + Validate.notNull(theInterceptor); + synchronized (myRegistryMutex) { + myAnonymousInvokers.put(thePointcut, new AnonymousLambdaInvoker(thePointcut, theInterceptor, theOrder)); + } } @Override @VisibleForTesting public void clearAnonymousHookForUnitTest() { - myAnonymousInvokers.clear(); + synchronized (myRegistryMutex) { + myAnonymousInvokers.clear(); + } + } + + @Override + public void unregisterInterceptors(@Nullable Collection theInterceptors) { + if (theInterceptors != null) { + theInterceptors.forEach(t -> unregisterInterceptor(t)); + } } @Override @@ -125,7 +151,7 @@ public class InterceptorService implements IInterceptorRegistry, IInterceptorBro } @Override - public boolean registerInterceptor(Object theInterceptor) { + public boolean registerAnonymousInterceptor(Object theInterceptor) { synchronized (myRegistryMutex) { if (isInterceptorAlreadyRegistered(theInterceptor)) { @@ -159,12 +185,13 @@ public class InterceptorService implements IInterceptorRegistry, IInterceptorBro synchronized (myRegistryMutex) { myInterceptors.removeIf(t -> t == theInterceptor); myGlobalInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor); + myAnonymousInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor); } } @Override public boolean registerGlobalInterceptor(Object theInterceptor) { - return registerInterceptor(theInterceptor); + return registerAnonymousInterceptor(theInterceptor); } @Override @@ -281,8 +308,8 @@ public class InterceptorService implements IInterceptorRegistry, IInterceptorBro retVal = Arrays .stream(theInvokersLists) - .filter(t->t!= null) - .flatMap(t->t.stream()) + .filter(t -> t != null) + .flatMap(t -> t.stream()) .sorted() .collect(Collectors.toList()); @@ -312,16 +339,18 @@ public class InterceptorService implements IInterceptorRegistry, IInterceptorBro } private class AnonymousLambdaInvoker extends BaseInvoker { - private final IAnonymousLambdaHook myHook; + private final IAnonymousInterceptor myHook; + private final Pointcut myPointcut; - public AnonymousLambdaInvoker(IAnonymousLambdaHook theHook, int theOrder) { + public AnonymousLambdaInvoker(Pointcut thePointcut, IAnonymousInterceptor theHook, int theOrder) { super(theHook, theOrder); myHook = theHook; + myPointcut = thePointcut; } @Override boolean invoke(HookParams theParams) { - myHook.invoke(theParams); + myHook.invoke(myPointcut, theParams); return true; } } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/PerformanceMessage.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/PerformanceMessage.java new file mode 100644 index 00000000000..90c4d180349 --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/PerformanceMessage.java @@ -0,0 +1,16 @@ +package ca.uhn.fhir.jpa.model.search; + +public class PerformanceMessage { + + private String myMessage; + + public String getMessage() { + return myMessage; + } + + public PerformanceMessage setMessage(String theMessage) { + myMessage = theMessage; + return this; + } + +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/SearchRuntimeDetails.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/SearchRuntimeDetails.java index 95a888b60a8..90a5a842277 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/SearchRuntimeDetails.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/SearchRuntimeDetails.java @@ -12,6 +12,7 @@ public class SearchRuntimeDetails { private int myFoundMatchesCount; private boolean myLoadSynchronous; private String myQueryString; + private SearchStatusEnum mySearchStatus; public SearchRuntimeDetails(String theSearchUuid) { mySearchUuid = theSearchUuid; @@ -52,4 +53,12 @@ public class SearchRuntimeDetails { public String getQueryString() { return myQueryString; } + + public SearchStatusEnum getSearchStatus() { + return mySearchStatus; + } + + public void setSearchStatus(SearchStatusEnum theSearchStatus) { + mySearchStatus = theSearchStatus; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SearchStatusEnum.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/SearchStatusEnum.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SearchStatusEnum.java rename to hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/SearchStatusEnum.java index fa6f8e16774..52de69710e7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SearchStatusEnum.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/search/SearchStatusEnum.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.entity; +package ca.uhn.fhir.jpa.model.search; /*- * #%L diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/interceptor/executor/InterceptorServiceTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/interceptor/executor/InterceptorServiceTest.java index cbd279d4f9c..e9a554dba52 100644 --- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/interceptor/executor/InterceptorServiceTest.java +++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/interceptor/executor/InterceptorServiceTest.java @@ -1,6 +1,9 @@ package ca.uhn.fhir.jpa.model.interceptor.executor; -import ca.uhn.fhir.jpa.model.interceptor.api.*; +import ca.uhn.fhir.jpa.model.interceptor.api.Hook; +import ca.uhn.fhir.jpa.model.interceptor.api.HookParams; +import ca.uhn.fhir.jpa.model.interceptor.api.Interceptor; +import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.StopWatch; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -14,7 +17,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.test.context.ContextConfiguration; @@ -50,7 +52,7 @@ public class InterceptorServiceTest { int initialSize = myInterceptorRegistry.getGlobalInterceptorsForUnitTest().size(); try { - myInterceptorRegistry.registerInterceptor(new InterceptorThatFailsOnRegister()); + myInterceptorRegistry.registerAnonymousInterceptor(new InterceptorThatFailsOnRegister()); fail(); } catch (InternalErrorException e) { // good @@ -72,7 +74,7 @@ public class InterceptorServiceTest { public void testManuallyRegisterGlobalInterceptor() { // Register the manual interceptor (has @Order right in the middle) - myInterceptorRegistry.registerInterceptor(myInterceptorManual); + myInterceptorRegistry.registerAnonymousInterceptor(myInterceptorManual); List globalInterceptors = myInterceptorRegistry.getGlobalInterceptorsForUnitTest(); assertEquals(3, globalInterceptors.size()); assertTrue(globalInterceptors.get(0).getClass().toString(), globalInterceptors.get(0) instanceof MyTestInterceptorOne); @@ -80,7 +82,7 @@ public class InterceptorServiceTest { assertTrue(globalInterceptors.get(2).getClass().toString(), globalInterceptors.get(2) instanceof MyTestInterceptorTwo); // Try to register again (should have no effect - myInterceptorRegistry.registerInterceptor(myInterceptorManual); + myInterceptorRegistry.registerAnonymousInterceptor(myInterceptorManual); globalInterceptors = myInterceptorRegistry.getGlobalInterceptorsForUnitTest(); assertEquals(3, globalInterceptors.size()); assertTrue(globalInterceptors.get(0).getClass().toString(), globalInterceptors.get(0) instanceof MyTestInterceptorOne); @@ -263,7 +265,7 @@ public class InterceptorServiceTest { * */ @Test - @Ignore + @Ignore("Performance test - Not needed normally") public void testThreadLocalHookInterceptorMicroBenchmark() { threadLocalMicroBenchmark(true, 500000); threadLocalMicroBenchmark(false, 500000); @@ -328,7 +330,6 @@ public class InterceptorServiceTest { } @Configuration - @ComponentScan(basePackages = "ca.uhn.fhir.jpa.model") static class InterceptorRegistryTestCtxConfig { @Bean @@ -343,7 +344,7 @@ public class InterceptorServiceTest { @Bean public MyTestInterceptorTwo interceptor1() { MyTestInterceptorTwo retVal = new MyTestInterceptorTwo(); - interceptorService().registerInterceptor(retVal); + interceptorService().registerAnonymousInterceptor(retVal); return retVal; } @@ -354,7 +355,7 @@ public class InterceptorServiceTest { @Bean public MyTestInterceptorOne interceptor2() { MyTestInterceptorOne retVal = new MyTestInterceptorOne(); - interceptorService().registerInterceptor(retVal); + interceptorService().registerAnonymousInterceptor(retVal); return retVal; } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/PointcutLatch.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/PointcutLatch.java index 0bcd02c33b2..1493b926270 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/PointcutLatch.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/PointcutLatch.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.subscription.module; import ca.uhn.fhir.jpa.model.interceptor.api.HookParams; -import ca.uhn.fhir.jpa.model.interceptor.api.IAnonymousLambdaHook; +import ca.uhn.fhir.jpa.model.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,7 +15,7 @@ import java.util.stream.Collectors; import static org.junit.Assert.*; -public class PointcutLatch implements IAnonymousLambdaHook { +public class PointcutLatch implements IAnonymousInterceptor { private static final Logger ourLog = LoggerFactory.getLogger(PointcutLatch.class); private static final int DEFAULT_TIMEOUT_SECONDS = 10; private static final FhirObjectPrinter ourFhirObjectToStringMapper = new FhirObjectPrinter(); @@ -25,9 +25,11 @@ public class PointcutLatch implements IAnonymousLambdaHook { private CountDownLatch myCountdownLatch; private AtomicReference myFailure; private AtomicReference> myCalledWith; + private Pointcut myPointcut; public PointcutLatch(Pointcut thePointcut) { this.name = thePointcut.name(); + myPointcut = thePointcut; } public PointcutLatch(String theName) { @@ -105,7 +107,7 @@ public class PointcutLatch implements IAnonymousLambdaHook { @Override - public void invoke(HookParams theArgs) { + public void invoke(Pointcut thePointcut, HookParams theArgs) { if (myCountdownLatch == null) { throw new PointcutLatchException("invoke() called before setExpectedCount() called.", theArgs); } else if (myCountdownLatch.getCount() <= 0) { @@ -121,7 +123,7 @@ public class PointcutLatch implements IAnonymousLambdaHook { } public void call(Object arg) { - this.invoke(new HookParams(arg)); + this.invoke(myPointcut, new HookParams(arg)); } private class PointcutLatchException extends IllegalStateException { diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java index 6ec7ee0895e..ed8faffc058 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java @@ -78,8 +78,8 @@ public abstract class BaseBlockingQueueSubscribableChannelDstu3Test extends Base ourSubscribableChannel = mySubscriptionChannelFactory.newDeliveryChannel("test", Subscription.SubscriptionChannelType.RESTHOOK.toCode().toLowerCase()); ourSubscribableChannel.subscribe(myStandaloneSubscriptionMessageHandler); } - myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED, mySubscriptionMatchingPost); - myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED, mySubscriptionActivatedPost); + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED, mySubscriptionMatchingPost); + myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED, mySubscriptionActivatedPost); } @After @@ -171,7 +171,7 @@ public abstract class BaseBlockingQueueSubscribableChannelDstu3Test extends Base public MethodOutcome update(@ResourceParam Observation theObservation, HttpServletRequest theRequest) { ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", "")); ourUpdatedObservations.add(theObservation); - updateLatch.invoke(new HookParams().add(Observation.class, theObservation)); + updateLatch.invoke(null, new HookParams().add(Observation.class, theObservation)); ourLog.info("Received Listener Update (now have {} updates)", ourUpdatedObservations.size()); return new MethodOutcome(new IdType("Observation/1"), false); }