From 84a34eb3c9bd1defdb999d099b2446f773ea1123 Mon Sep 17 00:00:00 2001 From: Ken Stevens Date: Tue, 18 Dec 2018 13:09:06 -0500 Subject: [PATCH] Subscription module support (#1147) * Reorganizing packages and dependencies to support standalone subscription running within a CDR container where all hapi modules are on the classpath. Moved Subscription registry out of interceptor and introduced SubscriptionLoader * Created ActiveSubscription and moved cache bits into it * Moved ExecutorQueue stuff out into its own class * Add test and supporting code to validate SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION behaviour * Added SubscriptionCheckingSubscriber * Moved a few beans to @ComponentScan * Replaced use of beanFactory with concrete factory classes * Switched test to use subscribablechannel * Added SubscriptionLoaderFhirClientTest * Confirm that our SubscriptionProviderFhirClient works with a live fhir client * Register interceptors with DaoConfig instead of RestServer. Also, Rename @VisibleForTesting methods with ForUnitTest * Fix triggering service so it uses new subscriptionmatcherinterceptor * Renamed "Database" classes to "Dao" * processing -> matching naming change --- example-projects/README.md | 11 + .../fhir/jpa/cds/example/CdsExampleTests.java | 8 +- .../ca/uhn/fhir/jpa/demo/JpaServerDemo.java | 27 +- .../uhn/fhir/jpa/demo/JpaServerDemoDstu2.java | 27 +- .../ca/uhn/fhir/jpa/demo/JpaServerDemo.java | 27 +- .../ca/uhn/fhir/jpa/demo/JpaServerDemo.java | 14 +- .../ca/uhn/fhir/jpa/config/BaseConfig.java | 46 +- .../uhn/fhir/jpa/config/BaseDstu2Config.java | 2 +- .../jpa/config/WebsocketDispatcherConfig.java | 4 +- .../jpa/config/dstu3/BaseDstu3Config.java | 2 +- .../uhn/fhir/jpa/config/r4/BaseR4Config.java | 2 +- .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 23 +- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 8 +- .../java/ca/uhn/fhir/jpa/dao/DaoConfig.java | 65 +- ...vider.java => DaoSearchParamProvider.java} | 6 +- .../fhir/jpa/dao/SearchBuilderFactory.java | 10 + ...lver.java => DaoResourceLinkResolver.java} | 4 +- ...r.java => DaoSearchParamSynchronizer.java} | 2 +- ...rchParamWithInlineReferencesExtractor.java | 10 +- .../BaseSubscriptionInterceptor.java | 630 ------------------ .../BaseSubscriptionSubscriber.java | 98 --- .../subscription/DaoResourceRetriever.java | 32 + .../SubscriptionActivatingInterceptor.java | 247 +++++++ .../SubscriptionActivatingSubscriber.java | 220 ------ .../SubscriptionInterceptorLoader.java | 34 + .../SubscriptionMatcherInterceptor.java | 127 ++++ .../SubscriptionTriggeringSvcImpl.java | 39 +- .../dbcache/DaoSubscriptionProvider.java | 35 + ...positeInMemoryDaoSubscriptionMatcher.java} | 30 +- .../DaoSubscriptionMatcher.java} | 16 +- .../email/SubscriptionEmailInterceptor.java | 89 --- .../matcher/ISubscriptionMatcher.java~HEAD | 7 - .../SubscriptionRestHookInterceptor.java | 46 -- .../ISubscriptionWebsocketHandler.java | 27 - .../SubscriptionWebsocketInterceptor.java | 43 -- .../ca/uhn/fhir/jpa/util/JpaConstants.java | 45 -- ...scriptionDeliveringRestHookSubscriber.java | 50 ++ .../uhn/fhir/jpa/config/TestDstu3Config.java | 11 +- .../ca/uhn/fhir/jpa/config/TestJPAConfig.java | 20 +- .../ca/uhn/fhir/jpa/config/TestR4Config.java | 10 +- .../fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java | 3 + ...sourceDaoDstu3InvalidSubscriptionTest.java | 21 +- .../ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java | 6 +- ...rResourceDaoR4InvalidSubscriptionTest.java | 22 +- .../dao/r4/SearchParamExtractorR4Test.java | 6 + .../BaseResourceProviderDstu2Test.java | 3 - .../dstu3/BaseResourceProviderDstu3Test.java | 6 - .../r4/BaseResourceProviderR4Test.java | 33 +- .../r4/ResourceProviderInterceptorR4Test.java | 2 - .../{r4 => }/BaseSubscriptionsR4Test.java | 32 +- .../{r4 => }/CountingInterceptor.java | 2 +- .../FhirClientSearchParamProviderTest.java | 17 +- .../FhirClientSubscriptionProviderTest.java | 61 ++ .../jpa/subscription/{r4 => }/FhirR4Util.java | 9 +- .../subscription/SubscriptionTestUtil.java | 86 +++ .../EmailSubscriptionDstu2Test.java | 50 +- .../email/EmailSubscriptionDstu3Test.java | 42 +- .../email/JavaMailEmailSenderTest.java | 4 +- .../InMemorySubscriptionMatcherTestR4.java} | 18 +- ...tivatesPreExistingSubscriptionsR4Test.java | 30 +- .../{ => resthook}/RestHookTestDstu2Test.java | 28 +- .../{ => resthook}/RestHookTestDstu3Test.java | 14 +- .../{r4 => resthook}/RestHookTestR4Test.java | 216 ++++-- ...rceptorRegisteredToDaoConfigDstu2Test.java | 15 +- ...rceptorRegisteredToDaoConfigDstu3Test.java | 50 +- ...nterceptorRegisteredToDaoConfigR4Test.java | 47 +- .../RestHookWithEventDefinitionR4Test.java | 15 +- .../SubscriptionTriggeringDstu3Test.java | 13 +- .../WebsocketWithCriteriaDstu2Test.java | 6 +- .../WebsocketWithCriteriaDstu3Test.java | 6 +- .../WebsocketWithCriteriaR4Test.java | 5 +- .../WebsocketWithSubscriptionIdDstu2Test.java | 18 +- .../WebsocketWithSubscriptionIdDstu3Test.java | 18 +- .../WebsocketWithSubscriptionIdR4Test.java | 17 +- hapi-fhir-jpaserver-example/README.md | 18 +- .../ca/uhn/fhir/jpa/demo/JpaServerDemo.java | 39 +- .../uhn/fhir/jpa/demo/JpaServerDemoDstu2.java | 32 +- .../fhir/jpa/model/entity/ModelConfig.java | 45 +- .../extractor/ResourceLinkExtractor.java | 6 - .../registry/BaseSearchParamRegistry.java | 34 +- .../registry/ISearchParamRegistry.java | 4 + .../registry/SearchParamRegistryDstu2.java | 4 - .../registry/SearchParamRegistryDstu3.java | 4 - .../registry/SearchParamRegistryR4.java | 6 - .../SearchParamExtractorDstu3Test.java | 6 + hapi-fhir-jpaserver-subscription/pom.xml | 51 ++ .../module}/CanonicalSubscription.java | 8 +- .../CanonicalSubscriptionChannelType.java | 115 ++++ ...inkedBlockingQueueSubscriptionChannel.java | 12 + .../{ => module}/ResourceModifiedMessage.java | 14 +- .../module/SubscriptionChannel.java | 83 +++ .../module/cache/ActiveSubscription.java | 73 ++ .../module/cache/ActiveSubscriptionCache.java | 52 ++ ...ockingQueueSubscriptionChannelFactory.java | 22 + .../cache/ISubscriptionChannelFactory.java | 9 + .../module/cache/ISubscriptionProvider.java | 11 + .../cache/SubscriptionCannonicalizer.java | 149 +++++ .../module/cache/SubscriptionConstants.java | 70 ++ .../SubscriptionDeliveryHandlerFactory.java | 37 + .../module/cache/SubscriptionLoader.java | 103 +++ .../module/cache/SubscriptionRegistry.java | 116 ++++ .../config/BaseSubscriptionConfig.java | 17 +- .../config/SubscriptionDstu3Config.java} | 6 +- .../matcher/CriteriaResourceMatcher.java | 2 +- .../matcher/ISubscriptionMatcher.java | 4 +- .../matcher/InMemorySubscriptionMatcher.java} | 10 +- .../matcher/InlineResourceLinkResolver.java | 2 +- .../matcher/SubscriptionMatchResult.java | 2 +- .../FhirClientResourceRetriever.java | 30 + .../FhirClientSearchParamProvider.java | 11 +- .../FhirClientSubscriptionProvider.java | 51 ++ .../StandaloneSubscriptionMessageHandler.java | 45 ++ .../module/subscriber}/BaseJsonMessage.java | 2 +- .../BaseSubscriptionDeliverySubscriber.java | 31 +- .../module/subscriber/IResourceRetriever.java | 8 + .../ResourceDeliveryJsonMessage.java | 2 +- .../subscriber}/ResourceDeliveryMessage.java | 10 +- .../ResourceModifiedJsonMessage.java | 5 +- .../SubscriptionCheckingSubscriber.java | 88 +-- ...scriptionDeliveringRestHookSubscriber.java | 44 +- .../SubscriptionWebsocketHandler.java | 36 +- .../subscriber}/email/EmailDetails.java | 2 +- .../subscriber}/email/IEmailSender.java | 2 +- .../email/JavaMailEmailSender.java | 2 +- ...SubscriptionDeliveringEmailSubscriber.java | 34 +- .../BaseSubscriptionDstu3Test.java | 8 - .../subscription/BaseSubscriptionTest.java | 26 - .../config/TestSubscriptionDstu3Config.java | 25 - .../module/BaseSubscriptionDstu3Test.java | 40 ++ .../module/BaseSubscriptionTest.java | 39 ++ .../module/ResourceModifiedTest.java | 50 ++ .../MockFhirClientSearchParamProvider.java} | 8 +- .../MockFhirClientSubscriptionProvider.java | 24 + .../config/TestSubscriptionConfig.java | 8 +- .../config/TestSubscriptionDstu3Config.java | 22 + .../InMemorySubscriptionMatcherTestR3.java} | 20 +- .../BaseSubscriptionChannelDstu3Test.java | 175 +++++ .../SubscriptionLoaderFhirClientTest.java | 42 ++ .../SubscriptionCheckingSubscriberTest.java | 69 ++ .../ca/uhn/fhirtest/TestRestfulServer.java | 11 - .../uhn/fhir/rest/server/RestfulServer.java | 4 +- .../autoconfigure/FhirAutoConfiguration.java | 3 +- .../ca/uhn/fhir/jpa/test/OverlayTestApp.java | 4 +- .../validation/FhirInstanceValidator.java | 3 +- .../fhir/r4/validation/InstanceValidator.java | 2 +- .../QuestionnaireValidatorDstu3Test.java | 5 +- .../QuestionnaireValidatorR4Test.java | 1 - .../tinder/TinderSourcesGeneratorMojo.java | 15 +- src/changes/changes.xml | 48 ++ 149 files changed, 3171 insertions(+), 2125 deletions(-) create mode 100644 example-projects/README.md rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/{DatabaseSearchParamProvider.java => DaoSearchParamProvider.java} (93%) create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/{DatabaseResourceLinkResolver.java => DaoResourceLinkResolver.java} (97%) rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/{DatabaseSearchParamSynchronizer.java => DaoSearchParamSynchronizer.java} (98%) delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionSubscriber.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/DaoResourceRetriever.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingSubscriber.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionInterceptorLoader.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbcache/DaoSubscriptionProvider.java rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/{matcher/SubscriptionMatcherCompositeInMemoryDatabase.java => dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java} (52%) rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/{matcher/SubscriptionMatcherDatabase.java => dbmatcher/DaoSubscriptionMatcher.java} (87%) delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionEmailInterceptor.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java~HEAD delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/ISubscriptionWebsocketHandler.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketInterceptor.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/StoppableSubscriptionDeliveringRestHookSubscriber.java rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{r4 => }/BaseSubscriptionsR4Test.java (88%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{r4 => }/CountingInterceptor.java (92%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{r4 => }/FhirClientSearchParamProviderTest.java (84%) create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSubscriptionProviderTest.java rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{r4 => }/FhirR4Util.java (93%) create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTestUtil.java rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{ => email}/EmailSubscriptionDstu2Test.java (73%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{matcher/SubscriptionMatcherInMemoryTestR4.java => module/matcher/InMemorySubscriptionMatcherTestR4.java} (98%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{r4 => resthook}/RestHookActivatesPreExistingSubscriptionsR4Test.java (87%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{ => resthook}/RestHookTestDstu2Test.java (93%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{ => resthook}/RestHookTestDstu3Test.java (97%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{r4 => resthook}/RestHookTestR4Test.java (78%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{ => resthook}/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java (96%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{ => resthook}/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java (93%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{r4 => resthook}/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java (93%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{r4 => resthook}/RestHookWithEventDefinitionR4Test.java (90%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{ => resthook}/SubscriptionTriggeringDstu3Test.java (97%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{ => websocket}/WebsocketWithCriteriaDstu2Test.java (96%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{ => websocket}/WebsocketWithCriteriaDstu3Test.java (96%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{r4 => websocket}/WebsocketWithCriteriaR4Test.java (97%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{ => websocket}/WebsocketWithSubscriptionIdDstu2Test.java (92%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{ => websocket}/WebsocketWithSubscriptionIdDstu3Test.java (91%) rename hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/{r4 => websocket}/WebsocketWithSubscriptionIdR4Test.java (91%) rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module}/CanonicalSubscription.java (97%) create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionChannelType.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/LinkedBlockingQueueSubscriptionChannel.java rename hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/{ => module}/ResourceModifiedMessage.java (89%) create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionChannel.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscription.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/BlockingQueueSubscriptionChannelFactory.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionChannelFactory.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionProvider.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCannonicalizer.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionDeliveryHandlerFactory.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistry.java rename hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/{ => module}/config/BaseSubscriptionConfig.java (67%) rename hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/{config/BaseSubscriptionDstu3Config.java => module/config/SubscriptionDstu3Config.java} (92%) rename hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/{ => module}/matcher/CriteriaResourceMatcher.java (99%) rename hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/{ => module}/matcher/ISubscriptionMatcher.java (86%) rename hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/{matcher/SubscriptionMatcherInMemory.java => module/matcher/InMemorySubscriptionMatcher.java} (90%) rename hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/{ => module}/matcher/InlineResourceLinkResolver.java (97%) rename hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/{ => module}/matcher/SubscriptionMatchResult.java (97%) create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientResourceRetriever.java rename hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/{ => module/standalone}/FhirClientSearchParamProvider.java (84%) create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSubscriptionProvider.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber}/BaseJsonMessage.java (96%) rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber}/BaseSubscriptionDeliverySubscriber.java (62%) create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceRetriever.java rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber}/ResourceDeliveryJsonMessage.java (96%) rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber}/ResourceDeliveryMessage.java (89%) rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber}/ResourceModifiedJsonMessage.java (91%) rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber}/SubscriptionCheckingSubscriber.java (55%) rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber}/SubscriptionDeliveringRestHookSubscriber.java (79%) rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber}/SubscriptionWebsocketHandler.java (88%) rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber}/email/EmailDetails.java (96%) rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber}/email/IEmailSender.java (92%) rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber}/email/JavaMailEmailSender.java (98%) rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription => hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber}/email/SubscriptionDeliveringEmailSubscriber.java (73%) delete mode 100644 hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDstu3Test.java delete mode 100644 hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionTest.java delete mode 100644 hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionDstu3Config.java create mode 100644 hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionDstu3Test.java create mode 100644 hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java create mode 100644 hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedTest.java rename hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/{config/MockSearchParamProvider.java => module/config/MockFhirClientSearchParamProvider.java} (63%) create mode 100644 hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSubscriptionProvider.java rename hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/{ => module}/config/TestSubscriptionConfig.java (76%) create mode 100644 hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionDstu3Config.java rename hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/{matcher/SubscriptionMatcherInMemoryTestR3.java => module/matcher/InMemorySubscriptionMatcherTestR3.java} (96%) create mode 100644 hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseSubscriptionChannelDstu3Test.java create mode 100644 hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java create mode 100644 hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriberTest.java diff --git a/example-projects/README.md b/example-projects/README.md new file mode 100644 index 00000000000..5ef27babc43 --- /dev/null +++ b/example-projects/README.md @@ -0,0 +1,11 @@ +# Unsupported + +Most of the projects in this module are no longer supported. + +The test in hapi-fhir-jpaserver-cds-example is @Ignored until Chris Schuler is able to make a change to the pom +this module depends on. + +## Supported JPA Example: + +The supported HAPI-FHIR JPA example is available in the [hapi-fhir-jpaserver-starter](https://github.com/hapifhir/hapi-fhir-jpaserver-starter) +project within the [hapifhir](https://github.com/hapifhir) GitHub Organization. diff --git a/example-projects/hapi-fhir-jpaserver-cds-example/src/test/java/ca/uhn/fhir/jpa/cds/example/CdsExampleTests.java b/example-projects/hapi-fhir-jpaserver-cds-example/src/test/java/ca/uhn/fhir/jpa/cds/example/CdsExampleTests.java index 478accecb5b..c1fc5856894 100644 --- a/example-projects/hapi-fhir-jpaserver-cds-example/src/test/java/ca/uhn/fhir/jpa/cds/example/CdsExampleTests.java +++ b/example-projects/hapi-fhir-jpaserver-cds-example/src/test/java/ca/uhn/fhir/jpa/cds/example/CdsExampleTests.java @@ -10,11 +10,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppContext; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.Ignore; +import org.junit.*; import java.io.*; import java.net.HttpURLConnection; @@ -26,7 +22,7 @@ import java.util.Collection; import java.util.List; import java.util.Scanner; -// FIXME KHS +// TODO Remove @Ignore once Chris Schuler has fixed the external jar this project depends on @Ignore public class CdsExampleTests { private static IGenericClient ourClient; diff --git a/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java b/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java index 9dfa49e7a21..b6c86dc655b 100644 --- a/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java +++ b/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java @@ -1,16 +1,6 @@ package ca.uhn.fhir.jpa.demo; -import java.util.Collection; -import java.util.List; - -import javax.servlet.ServletException; - -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Meta; -import org.springframework.web.context.ContextLoaderListener; -import org.springframework.web.context.WebApplicationContext; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.jpa.dao.DaoConfig; @@ -20,13 +10,20 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Meta; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.WebApplicationContext; + +import javax.servlet.ServletException; +import java.util.List; public class JpaServerDemo extends RestfulServer { @@ -129,12 +126,10 @@ public class JpaServerDemo extends RestfulServer { setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); /* - * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java) + * Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes() */ - Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values(); - for (IServerInterceptor interceptor : interceptorBeans) { - this.registerInterceptor(interceptor); - } + SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class); + subscriptionInterceptorLoader.registerInterceptors(); /* * If you are hosting this server at a specific DNS name, the server will try to diff --git a/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java b/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java index cf6bb8572ff..3c802e6cb10 100644 --- a/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java +++ b/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java @@ -1,16 +1,6 @@ package ca.uhn.fhir.jpa.demo; -import java.util.Collection; -import java.util.List; - -import javax.servlet.ServletException; - -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Meta; -import org.springframework.web.context.ContextLoaderListener; -import org.springframework.web.context.WebApplicationContext; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.jpa.dao.DaoConfig; @@ -20,13 +10,20 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Meta; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.WebApplicationContext; + +import javax.servlet.ServletException; +import java.util.List; public class JpaServerDemoDstu2 extends RestfulServer { @@ -129,12 +126,10 @@ public class JpaServerDemoDstu2 extends RestfulServer { setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); /* - * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java) + * Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes() */ - Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values(); - for (IServerInterceptor interceptor : interceptorBeans) { - this.registerInterceptor(interceptor); - } + SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class); + subscriptionInterceptorLoader.registerInterceptors(); /* * If you are hosting this server at a specific DNS name, the server will try to diff --git a/example-projects/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java b/example-projects/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java index f45045cf755..93ae03951b4 100644 --- a/example-projects/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java +++ b/example-projects/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java @@ -1,14 +1,5 @@ package ca.uhn.fhir.jpa.demo; -import java.util.Collection; -import java.util.List; - -import javax.servlet.ServletException; - -import org.hl7.fhir.dstu3.model.Meta; -import org.springframework.web.context.ContextLoaderListener; -import org.springframework.web.context.WebApplicationContext; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; @@ -16,12 +7,18 @@ import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; -import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import org.hl7.fhir.dstu3.model.Meta; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.WebApplicationContext; + +import javax.servlet.ServletException; +import java.util.List; public class JpaServerDemo extends RestfulServer { @@ -96,12 +93,10 @@ public class JpaServerDemo extends RestfulServer { setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); /* - * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java) + * Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes() */ - Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values(); - for (IServerInterceptor interceptor : interceptorBeans) { - this.registerInterceptor(interceptor); - } + SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class); + subscriptionInterceptorLoader.registerInterceptors(); /* * If you are hosting this server at a specific DNS name, the server will try to diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java index b4972e3e99d..c2bff0b1cfd 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java @@ -12,6 +12,7 @@ import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4; +import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; @@ -21,13 +22,11 @@ import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; import javax.servlet.ServletException; import java.util.ArrayList; -import java.util.Collection; import java.util.List; public class JpaServerDemo extends RestfulServer { @@ -143,19 +142,14 @@ public class JpaServerDemo extends RestfulServer { CorsInterceptor corsInterceptor = new CorsInterceptor(); registerInterceptor(corsInterceptor); - /* - * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java) - */ - Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values(); - for (IServerInterceptor interceptor : interceptorBeans) { - this.registerInterceptor(interceptor); - } - DaoConfig daoConfig = myAppCtx.getBean(DaoConfig.class); daoConfig.setAllowExternalReferences(ContextHolder.isAllowExternalRefs()); daoConfig.setEnforceReferentialIntegrityOnDelete(!ContextHolder.isDisableReferentialIntegrity()); daoConfig.setEnforceReferentialIntegrityOnWrite(!ContextHolder.isDisableReferentialIntegrity()); daoConfig.setReuseCachedSearchResultsForMillis(ContextHolder.getReuseCachedSearchResultsForMillis()); + + SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class); + subscriptionInterceptorLoader.registerInterceptors(); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index 18e151eb601..40141da5113 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -2,18 +2,18 @@ package ca.uhn.fhir.jpa.config; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.i18n.HapiLocalizer; -import ca.uhn.fhir.jpa.dao.DatabaseSearchParamProvider; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; -import ca.uhn.fhir.jpa.subscription.config.BaseSubscriptionConfig; -import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor; -import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; -import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; +import ca.uhn.fhir.jpa.subscription.dbmatcher.CompositeInMemoryDaoSubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.dbmatcher.DaoSubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory; +import ca.uhn.fhir.jpa.subscription.module.cache.BlockingQueueSubscriptionChannelFactory; +import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher; import org.hibernate.jpa.HibernatePersistenceProvider; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; @@ -59,7 +59,8 @@ import javax.annotation.Nonnull; @EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data") @ComponentScan(basePackages = "ca.uhn.fhir.jpa", excludeFilters={ @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=BaseConfig.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=WebSocketConfigurer.class)}) + @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=WebSocketConfigurer.class), + @ComponentScan.Filter(type=FilterType.REGEX, pattern="ca.uhn.fhir.jpa.subscription.module.standalone.*")}) public abstract class BaseConfig implements SchedulingConfigurer { @@ -132,34 +133,29 @@ public abstract class BaseConfig implements SchedulingConfigurer { } @Bean - protected ISearchParamProvider searchParamProvider() { - return new DatabaseSearchParamProvider(); + public InMemorySubscriptionMatcher inMemorySubscriptionMatcher() { + return new InMemorySubscriptionMatcher(); + } + + @Bean + public DaoSubscriptionMatcher daoSubscriptionMatcher() { + return new DaoSubscriptionMatcher(); } /** - * Note: If you're going to use this, you need to provide a bean - * of type {@link ca.uhn.fhir.jpa.subscription.email.IEmailSender} - * in your own Spring config + * Create a @Primary @Bean if you need a different implementation */ @Bean - @Lazy - public SubscriptionEmailInterceptor subscriptionEmailInterceptor() { - return new SubscriptionEmailInterceptor(); + public ISubscriptionChannelFactory blockingQueueSubscriptionDeliveryChannelFactory() { + return new BlockingQueueSubscriptionChannelFactory(); } @Bean - @Lazy - public SubscriptionRestHookInterceptor subscriptionRestHookInterceptor() { - return new SubscriptionRestHookInterceptor(); + @Primary + public ISubscriptionMatcher subscriptionMatcherCompositeInMemoryDatabase() { + return new CompositeInMemoryDaoSubscriptionMatcher(daoSubscriptionMatcher(), inMemorySubscriptionMatcher()); } - @Bean - @Lazy - public SubscriptionWebsocketInterceptor subscriptionWebsocketInterceptor() { - return new SubscriptionWebsocketInterceptor(); - } - - public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) { theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer())); theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java index f0ca4a9eb7d..88c71f6b89e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java @@ -116,7 +116,7 @@ public class BaseDstu2Config extends BaseConfig { @Bean public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryDstu2(searchParamProvider()); + return new SearchParamRegistryDstu2(); } @Bean(name = "mySystemDaoDstu2", autowire = Autowire.BY_NAME) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java index 383b52ea84a..c7b31cdc26f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java @@ -20,9 +20,7 @@ package ca.uhn.fhir.jpa.config; * #L% */ -import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketHandler; -import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionWebsocketHandler; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java index 22eb20d0a8b..38060e32461 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java @@ -123,7 +123,7 @@ public class BaseDstu3Config extends BaseConfig { @Bean public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryDstu3(searchParamProvider()); + return new SearchParamRegistryDstu3(); } @Bean(name = "mySystemDaoDstu3", autowire = Autowire.BY_NAME) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java index fdc4e637a78..5224694a797 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java @@ -138,7 +138,7 @@ public class BaseR4Config extends BaseConfig { @Bean public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryR4(searchParamProvider()); + return new SearchParamRegistryR4(); } @Bean(name = "mySystemDaoR4", autowire = Autowire.BY_NAME) 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 233c9850ec5..cc514088696 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 @@ -2,16 +2,16 @@ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.context.*; import ca.uhn.fhir.jpa.dao.data.*; -import ca.uhn.fhir.jpa.dao.index.*; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor; import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; -import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; import ca.uhn.fhir.jpa.searchparam.extractor.LogicalReferenceHelper; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; -import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; @@ -59,7 +59,6 @@ import org.hibernate.internal.SessionImpl; import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.r4.model.Bundle.HTTPVerb; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -78,8 +77,6 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.xml.stream.events.Characters; import javax.xml.stream.events.XMLEvent; -import java.io.CharArrayWriter; -import java.text.Normalizer; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicInteger; @@ -178,13 +175,13 @@ public abstract class BaseHapiFhirDao implements IDao, //@Autowired //private ISearchResultDao mySearchResultDao; @Autowired - private BeanFactory beanFactory; - @Autowired private DaoRegistry myDaoRegistry; @Autowired private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor; @Autowired - private DatabaseSearchParamSynchronizer myDatabaseSearchParamSynchronizer; + private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer; + @Autowired + private SearchBuilderFactory mySearchBuilderFactory; private ApplicationContext myApplicationContext; @@ -746,9 +743,9 @@ public abstract class BaseHapiFhirDao implements IDao, return LogicalReferenceHelper.isLogicalReference(myConfig.getModelConfig(), theId); } - @Override + // TODO KHS inject a searchBuilderFactory into callers of this method and delete this method public SearchBuilder newSearchBuilder() { - return beanFactory.getBean(SearchBuilder.class, this); + return mySearchBuilderFactory.newSearchBuilder(this); } public void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) { @@ -1406,7 +1403,7 @@ public abstract class BaseHapiFhirDao implements IDao, * Indexing */ if (thePerformIndexing) { - myDatabaseSearchParamSynchronizer.synchronizeSearchParamsToDatabase(newParams, theEntity, existingParams); + myDaoSearchParamSynchronizer.synchronizeSearchParamsToDatabase(newParams, theEntity, existingParams); mySearchParamWithInlineReferencesExtractor.storeCompositeStringUniques(newParams, theEntity, existingParams); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 56476f422fd..a8ef25c7bee 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -24,13 +24,11 @@ import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; import ca.uhn.fhir.jpa.dao.r4.MatchResourceUrlService; import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.ExpungeOptions; @@ -73,17 +71,19 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; public abstract class BaseHapiFhirResourceDao extends BaseHapiFhirDao implements IFhirResourceDao { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class); + @Autowired protected PlatformTransactionManager myPlatformTransactionManager; @Autowired(required = false) protected IFulltextSearchSvc mySearchDao; @Autowired protected DaoConfig myDaoConfig; + @Autowired + private MatchResourceUrlService myMatchResourceUrlService; + private String myResourceName; private Class myResourceType; private String mySecondaryPrimaryKeyParamName; - @Autowired - private MatchResourceUrlService myMatchResourceUrlService; @Override public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java index 022c4e3419d..76c7a03bab7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java @@ -4,12 +4,12 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum; import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; -import ca.uhn.fhir.jpa.util.JpaConstants; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Sets; -import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; +import org.hl7.fhir.instance.model.Subscription; import org.hl7.fhir.r4.model.Bundle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -112,7 +112,7 @@ public class DaoConfig { * update setter javadoc if default changes */ private boolean myIndexContainedResources = true; - private List myInterceptors; + private List myInterceptors = new ArrayList<>(); /** * update setter javadoc if default changes */ @@ -484,14 +484,26 @@ public class DaoConfig { * Returns the interceptors which will be notified of operations. * * @see #setInterceptors(List) + * @deprecated Marked as deprecated as of HAPI 3.7.0. Use {@link #registerInterceptor} or {@link #unregisterInterceptor}instead. */ + + @Deprecated public List getInterceptors() { - if (myInterceptors == null) { - myInterceptors = new ArrayList<>(); - } return myInterceptors; } + public void registerInterceptor(IServerInterceptor theInterceptor) { + Validate.notNull(theInterceptor, "Interceptor can not be null"); + if (!myInterceptors.contains(theInterceptor)) { + myInterceptors.add(theInterceptor); + } + } + + public void unregisterInterceptor(IServerInterceptor theInterceptor) { + Validate.notNull(theInterceptor, "Interceptor can not be null"); + myInterceptors.remove(theInterceptor); + } + /** * This may be used to optionally register server interceptors directly against the DAOs. */ @@ -1462,6 +1474,47 @@ public class DaoConfig { myModelConfig.setDefaultSearchParamsCanBeOverridden(theDefaultSearchParamsCanBeOverridden); } + /** + * This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted + * to the server matching these types will be activated. + * + */ + public DaoConfig addSupportedSubscriptionType(Subscription.SubscriptionChannelType theSubscriptionChannelType) { + myModelConfig.addSupportedSubscriptionType(theSubscriptionChannelType); + return this; + } + + /** + * This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted + * to the server matching these types will be activated. + * + */ + public Set getSupportedSubscriptionTypes() { + return myModelConfig.getSupportedSubscriptionTypes(); + } + + @VisibleForTesting + public void clearSupportedSubscriptionTypesForUnitTest() { + myModelConfig.clearSupportedSubscriptionTypesForUnitTest(); + } + + /** + * If e-mail subscriptions are supported, the From address used when sending e-mails + */ + + public String getEmailFromAddress() { + return myModelConfig.getEmailFromAddress(); + } + + /** + * If e-mail subscriptions are supported, the From address used when sending e-mails + */ + + public void setEmailFromAddress(String theEmailFromAddress) { + myModelConfig.setEmailFromAddress(theEmailFromAddress); + } + + public enum IndexEnabledEnum { ENABLED, diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DatabaseSearchParamProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java similarity index 93% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DatabaseSearchParamProvider.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java index 0c57157a14e..9ae8f6807e7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DatabaseSearchParamProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java @@ -20,17 +20,19 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.support.TransactionTemplate; -public class DatabaseSearchParamProvider implements ISearchParamProvider { +@Service +public class DaoSearchParamProvider implements ISearchParamProvider { @Autowired private PlatformTransactionManager myTxManager; @Autowired diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java new file mode 100644 index 00000000000..894db65762c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java @@ -0,0 +1,10 @@ +package ca.uhn.fhir.jpa.dao; + +import org.springframework.beans.factory.annotation.Lookup; +import org.springframework.stereotype.Service; + +@Service +public abstract class SearchBuilderFactory { + @Lookup + public abstract SearchBuilder newSearchBuilder(BaseHapiFhirDao theBaseHapiFhirResourceDao); +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DatabaseResourceLinkResolver.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java similarity index 97% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DatabaseResourceLinkResolver.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java index fe920db14bc..c55d7430acb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DatabaseResourceLinkResolver.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java @@ -41,8 +41,8 @@ import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; @Service -public class DatabaseResourceLinkResolver implements IResourceLinkResolver { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DatabaseResourceLinkResolver.class); +public class DaoResourceLinkResolver implements IResourceLinkResolver { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DaoResourceLinkResolver.class); @Autowired private DaoConfig myDaoConfig; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DatabaseSearchParamSynchronizer.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java similarity index 98% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DatabaseSearchParamSynchronizer.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java index 27fe5d219f7..9e7a3ad1d66 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DatabaseSearchParamSynchronizer.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java @@ -35,7 +35,7 @@ import java.util.Collection; import java.util.List; @Service -public class DatabaseSearchParamSynchronizer { +public class DaoSearchParamSynchronizer { @Autowired private DaoConfig myDaoConfig; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java index b8ba6b4efb6..631eab984e8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java @@ -77,9 +77,9 @@ public class SearchParamWithInlineReferencesExtractor { @Autowired ResourceLinkExtractor myResourceLinkExtractor; @Autowired - DatabaseResourceLinkResolver myDatabaseResourceLinkResolver; + DaoResourceLinkResolver myDaoResourceLinkResolver; @Autowired - DatabaseSearchParamSynchronizer myDatabaseSearchParamSynchronizer; + DaoSearchParamSynchronizer myDaoSearchParamSynchronizer; @Autowired private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao; @@ -99,7 +99,7 @@ public class SearchParamWithInlineReferencesExtractor { extractInlineReferences(theResource); - myResourceLinkExtractor.extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, myDatabaseResourceLinkResolver); + myResourceLinkExtractor.extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, myDaoResourceLinkResolver); /* * If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them @@ -258,12 +258,12 @@ public class SearchParamWithInlineReferencesExtractor { // Store composite string uniques if (myDaoConfig.isUniqueIndexesEnabled()) { - for (ResourceIndexedCompositeStringUnique next : myDatabaseSearchParamSynchronizer.subtract(existingParams.compositeStringUniques, theParams.compositeStringUniques)) { + for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(existingParams.compositeStringUniques, theParams.compositeStringUniques)) { ourLog.debug("Removing unique index: {}", next); myEntityManager.remove(next); theEntity.getParamsCompositeStringUnique().remove(next); } - for (ResourceIndexedCompositeStringUnique next : myDatabaseSearchParamSynchronizer.subtract(theParams.compositeStringUniques, existingParams.compositeStringUniques)) { + for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(theParams.compositeStringUniques, existingParams.compositeStringUniques)) { if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) { ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString()); if (existing != null) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java deleted file mode 100644 index e1418316073..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java +++ /dev/null @@ -1,630 +0,0 @@ -package ca.uhn.fhir.jpa.subscription; - -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jpa.config.BaseConfig; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails; -import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl; -import ca.uhn.fhir.jpa.searchparam.MatchUrlService; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.subscription.matcher.SubscriptionMatcherCompositeInMemoryDatabase; -import ca.uhn.fhir.jpa.subscription.matcher.SubscriptionMatcherDatabase; -import ca.uhn.fhir.jpa.util.JpaConstants; -import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.TokenOrListParam; -import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; -import ca.uhn.fhir.util.StopWatch; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; -import com.google.common.collect.Multimaps; -import org.apache.commons.lang3.Validate; -import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.IBaseReference; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.Subscription; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.core.task.AsyncTaskExecutor; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.messaging.support.ExecutorSubscribableChannel; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; -import org.springframework.transaction.support.TransactionSynchronizationAdapter; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.transaction.support.TransactionTemplate; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.util.*; -import java.util.concurrent.*; - -/*- - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2018 University Health Network - * %% - * 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% - */ - -public abstract class BaseSubscriptionInterceptor extends ServerOperationInterceptorAdapter { - - static final String SUBSCRIPTION_STATUS = "Subscription.status"; - static final String SUBSCRIPTION_TYPE = "Subscription.channel.type"; - private static final Integer MAX_SUBSCRIPTION_RESULTS = 1000; - private static boolean ourForcePayloadEncodeAndDecodeForUnitTests; - private final Object myInitSubscriptionsLock = new Object(); - private SubscribableChannel myProcessingChannel; - private Map myDeliveryChannel; - private ExecutorService myProcessingExecutor; - private int myExecutorThreadCount; - private SubscriptionActivatingSubscriber mySubscriptionActivatingSubscriber; - private MessageHandler mySubscriptionCheckingSubscriber; - private ConcurrentHashMap myIdToSubscription = new ConcurrentHashMap<>(); - private ConcurrentHashMap mySubscribableChannel = new ConcurrentHashMap<>(); - private Multimap myIdToDeliveryHandler = Multimaps.synchronizedListMultimap(ArrayListMultimap.create()); - private Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionInterceptor.class); - private ThreadPoolExecutor myDeliveryExecutor; - private LinkedBlockingQueue myProcessingExecutorQueue; - @Autowired - private FhirContext myCtx; - @Autowired(required = false) - @Qualifier("myEventDefinitionDaoR4") - private IFhirResourceDao myEventDefinitionDaoR4; - @Autowired() - private PlatformTransactionManager myTxManager; - @Autowired - @Qualifier(BaseConfig.TASK_EXECUTOR_NAME) - private AsyncTaskExecutor myAsyncTaskExecutor; - @Autowired - private SubscriptionMatcherCompositeInMemoryDatabase mySubscriptionMatcherCompositeInMemoryDatabase; - @Autowired - private SubscriptionMatcherDatabase mySubscriptionMatcherDatabase; - @Autowired - private DaoRegistry myDaoRegistry; - @Autowired - private BeanFactory beanFactory; - @Autowired - private MatchUrlService myMatchUrlService; - private Semaphore myInitSubscriptionsSemaphore = new Semaphore(1); - - /** - * Constructor - */ - public BaseSubscriptionInterceptor() { - super(); - setExecutorThreadCount(5); - } - - protected CanonicalSubscription canonicalize(S theSubscription) { - switch (myCtx.getVersion().getVersion()) { - case DSTU2: - return canonicalizeDstu2(theSubscription); - case DSTU3: - return canonicalizeDstu3(theSubscription); - case R4: - return canonicalizeR4(theSubscription); - default: - throw new ConfigurationException("Subscription not supported for version: " + myCtx.getVersion().getVersion()); - } - } - - protected CanonicalSubscription canonicalizeDstu2(IBaseResource theSubscription) { - ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription; - - CanonicalSubscription retVal = new CanonicalSubscription(); - try { - retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus())); - retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType())); - retVal.setCriteriaString(subscription.getCriteria()); - retVal.setEndpointUrl(subscription.getChannel().getEndpoint()); - retVal.setHeaders(subscription.getChannel().getHeader()); - retVal.setIdElement(subscription.getIdElement()); - retVal.setPayloadString(subscription.getChannel().getPayload()); - } catch (FHIRException theE) { - throw new InternalErrorException(theE); - } - return retVal; - } - - protected CanonicalSubscription canonicalizeDstu3(IBaseResource theSubscription) { - org.hl7.fhir.dstu3.model.Subscription subscription = (org.hl7.fhir.dstu3.model.Subscription) theSubscription; - - CanonicalSubscription retVal = new CanonicalSubscription(); - try { - retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus().toCode())); - retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode())); - retVal.setCriteriaString(subscription.getCriteria()); - retVal.setEndpointUrl(subscription.getChannel().getEndpoint()); - retVal.setHeaders(subscription.getChannel().getHeader()); - retVal.setIdElement(subscription.getIdElement()); - retVal.setPayloadString(subscription.getChannel().getPayload()); - - if (retVal.getChannelType() == Subscription.SubscriptionChannelType.EMAIL) { - String from; - String subjectTemplate; - String bodyTemplate; - try { - from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM); - subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE); - } catch (FHIRException theE) { - throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE); - } - retVal.getEmailDetails().setFrom(from); - retVal.getEmailDetails().setSubjectTemplate(subjectTemplate); - } - - if (retVal.getChannelType() == Subscription.SubscriptionChannelType.RESTHOOK) { - String stripVersionIds; - String deliverLatestVersion; - try { - stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS); - deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION); - } catch (FHIRException theE) { - throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE); - } - retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds)); - retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion)); - } - - } catch (FHIRException theE) { - throw new InternalErrorException(theE); - } - return retVal; - } - - protected CanonicalSubscription canonicalizeR4(IBaseResource theSubscription) { - org.hl7.fhir.r4.model.Subscription subscription = (org.hl7.fhir.r4.model.Subscription) theSubscription; - - CanonicalSubscription retVal = new CanonicalSubscription(); - retVal.setStatus(subscription.getStatus()); - retVal.setChannelType(subscription.getChannel().getType()); - retVal.setCriteriaString(subscription.getCriteria()); - retVal.setEndpointUrl(subscription.getChannel().getEndpoint()); - retVal.setHeaders(subscription.getChannel().getHeader()); - retVal.setIdElement(subscription.getIdElement()); - retVal.setPayloadString(subscription.getChannel().getPayload()); - - if (retVal.getChannelType() == Subscription.SubscriptionChannelType.EMAIL) { - String from; - String subjectTemplate; - try { - from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM); - subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE); - } catch (FHIRException theE) { - throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE); - } - retVal.getEmailDetails().setFrom(from); - retVal.getEmailDetails().setSubjectTemplate(subjectTemplate); - } - - if (retVal.getChannelType() == Subscription.SubscriptionChannelType.RESTHOOK) { - String stripVersionIds; - String deliverLatestVersion; - try { - stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS); - deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION); - } catch (FHIRException theE) { - throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE); - } - retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds)); - retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion)); - } - - List topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics"); - if (topicExts.size() > 0) { - IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive(); - if (!"EventDefinition".equals(ref.getReferenceElement().getResourceType())) { - throw new PreconditionFailedException("Topic reference must be an EventDefinition"); - } - - org.hl7.fhir.r4.model.EventDefinition def = myEventDefinitionDaoR4.read(ref.getReferenceElement()); - retVal.addTrigger(new CanonicalSubscription.CanonicalEventDefinition(def)); - } - - return retVal; - } - - protected SubscribableChannel createDeliveryChannel(CanonicalSubscription theSubscription) { - String subscriptionId = theSubscription.getIdElement(myCtx).getIdPart(); - - LinkedBlockingQueue executorQueue = new LinkedBlockingQueue<>(1000); - BasicThreadFactory threadFactory = new BasicThreadFactory.Builder() - .namingPattern("subscription-delivery-" + subscriptionId + "-%d") - .daemon(false) - .priority(Thread.NORM_PRIORITY) - .build(); - RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() { - @Override - public void rejectedExecution(Runnable theRunnable, ThreadPoolExecutor theExecutor) { - ourLog.info("Note: Executor queue is full ({} elements), waiting for a slot to become available!", executorQueue.size()); - StopWatch sw = new StopWatch(); - try { - executorQueue.put(theRunnable); - } catch (InterruptedException theE) { - throw new RejectedExecutionException("Task " + theRunnable.toString() + - " rejected from " + theE.toString()); - } - ourLog.info("Slot become available after {}ms", sw.getMillis()); - } - }; - ThreadPoolExecutor deliveryExecutor = new ThreadPoolExecutor( - 1, - getExecutorThreadCount(), - 0L, - TimeUnit.MILLISECONDS, - executorQueue, - threadFactory, - rejectedExecutionHandler); - - return new ExecutorSubscribableChannel(deliveryExecutor); - } - - /** - * Returns an empty handler if the interceptor will manually handle registration and unregistration - */ - protected abstract Optional createDeliveryHandler(CanonicalSubscription theSubscription); - - public abstract Subscription.SubscriptionChannelType getChannelType(); - - protected MessageChannel getDeliveryChannel(CanonicalSubscription theSubscription) { - return mySubscribableChannel.get(theSubscription.getIdElement(myCtx).getIdPart()); - } - - public int getExecutorQueueSizeForUnitTests() { - return myProcessingExecutorQueue.size(); - } - - public int getExecutorThreadCount() { - return myExecutorThreadCount; - } - - public void setExecutorThreadCount(int theExecutorThreadCount) { - Validate.inclusiveBetween(1, Integer.MAX_VALUE, theExecutorThreadCount); - myExecutorThreadCount = theExecutorThreadCount; - } - - public Map getIdToSubscription() { - return Collections.unmodifiableMap(myIdToSubscription); - } - - public SubscribableChannel getProcessingChannel() { - return myProcessingChannel; - } - - public void setProcessingChannel(SubscribableChannel theProcessingChannel) { - myProcessingChannel = theProcessingChannel; - } - - public List getRegisteredSubscriptions() { - return new ArrayList<>(myIdToSubscription.values()); - } - - public CanonicalSubscription hasSubscription(IIdType theId) { - Validate.notNull(theId); - Validate.notBlank(theId.getIdPart()); - return myIdToSubscription.get(theId.getIdPart()); - } - - /** - * Read the existing subscriptions from the database - */ - @SuppressWarnings("unused") - @Scheduled(fixedDelay = 60000) - public void initSubscriptions() { - if (!myInitSubscriptionsSemaphore.tryAcquire()) { - return; - } - try { - doInitSubscriptions(); - } finally { - myInitSubscriptionsSemaphore.release(); - } - } - - public Integer doInitSubscriptions() { - synchronized (myInitSubscriptionsLock) { - ourLog.debug("Starting init subscriptions"); - SearchParameterMap map = new SearchParameterMap(); - map.add(Subscription.SP_TYPE, new TokenParam(null, getChannelType().toCode())); - map.add(Subscription.SP_STATUS, new TokenOrListParam() - .addOr(new TokenParam(null, Subscription.SubscriptionStatus.REQUESTED.toCode())) - .addOr(new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode()))); - map.setLoadSynchronousUpTo(MAX_SUBSCRIPTION_RESULTS); - - RequestDetails req = new ServletSubRequestDetails(); - req.setSubRequest(true); - - IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao(); - IBundleProvider subscriptionBundleList = subscriptionDao.search(map, req); - if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) { - ourLog.error("Currently over " + MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded."); - } - - List resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size()); - - Set allIds = new HashSet<>(); - int changesCount = 0; - for (IBaseResource resource : resourceList) { - String nextId = resource.getIdElement().getIdPart(); - allIds.add(nextId); - boolean changed = mySubscriptionActivatingSubscriber.activateOrRegisterSubscriptionIfRequired(resource); - if (changed) { - changesCount++; - } - } - - unregisterAllSubscriptionsNotInCollection(allIds); - ourLog.trace("Finished init subscriptions - found {}", resourceList.size()); - - return changesCount; - } - } - - @SuppressWarnings("unused") - @PreDestroy - public void preDestroy() { - getProcessingChannel().unsubscribe(mySubscriptionCheckingSubscriber); - unregisterAllSubscriptionsNotInCollection(Collections.emptyList()); - } - - public void registerHandler(String theSubscriptionId, MessageHandler theHandler) { - mySubscribableChannel.get(theSubscriptionId).subscribe(theHandler); - myIdToDeliveryHandler.put(theSubscriptionId, theHandler); - } - - @SuppressWarnings("UnusedReturnValue") - public CanonicalSubscription registerSubscription(IIdType theId, S theSubscription) { - Validate.notNull(theId); - String subscriptionId = theId.getIdPart(); - Validate.notBlank(subscriptionId); - Validate.notNull(theSubscription); - - CanonicalSubscription canonicalized = canonicalize(theSubscription); - SubscribableChannel deliveryChannel = createDeliveryChannel(canonicalized); - Optional deliveryHandler = createDeliveryHandler(canonicalized); - - mySubscribableChannel.put(subscriptionId, deliveryChannel); - myIdToSubscription.put(subscriptionId, canonicalized); - - deliveryHandler.ifPresent(handler -> registerHandler(subscriptionId, handler)); - - return canonicalized; - } - - protected void registerSubscriptionCheckingSubscriber() { - if (mySubscriptionCheckingSubscriber == null) { - mySubscriptionCheckingSubscriber = beanFactory.getBean(SubscriptionCheckingSubscriber.class, getChannelType(), this, mySubscriptionMatcherCompositeInMemoryDatabase); - } - getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber); - } - - @Override - public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) { - submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE); - } - - @Override - public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) { - ResourceModifiedMessage msg = new ResourceModifiedMessage(); - msg.setId(theResource.getIdElement()); - msg.setOperationType(ResourceModifiedMessage.OperationTypeEnum.DELETE); - submitResourceModified(msg); - } - - @Override - public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) { - submitResourceModifiedForUpdate(theNewResource); - } - - void submitResourceModifiedForUpdate(IBaseResource theNewResource) { - submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE); - } - - private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) { - ResourceModifiedMessage msg = new ResourceModifiedMessage(); - msg.setId(theNewResource.getIdElement()); - msg.setOperationType(theOperationType); - msg.setNewPayload(myCtx, theNewResource); - if (ourForcePayloadEncodeAndDecodeForUnitTests) { - msg.clearPayloadDecoded(); - } - submitResourceModified(msg); - } - - protected void sendToProcessingChannel(final ResourceModifiedMessage theMessage) { - ourLog.trace("Registering synchronization to send resource modified message to processing channel"); - - /* - * We only actually submit this item work working after the - */ - if (TransactionSynchronizationManager.isSynchronizationActive()) { - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { - @Override - public void afterCommit() { - ourLog.trace("Sending resource modified message to processing channel"); - getProcessingChannel().send(new ResourceModifiedJsonMessage(theMessage)); - } - }); - } else { - ourLog.trace("Sending resource modified message to processing channel"); - getProcessingChannel().send(new ResourceModifiedJsonMessage(theMessage)); - } - } - - @VisibleForTesting - public void setAsyncTaskExecutorForUnitTest(AsyncTaskExecutor theAsyncTaskExecutor) { - myAsyncTaskExecutor = theAsyncTaskExecutor; - } - - public void setFhirContext(FhirContext theCtx) { - myCtx = theCtx; - } - - @VisibleForTesting - public void setTxManager(PlatformTransactionManager theTxManager) { - myTxManager = theTxManager; - } - - @PostConstruct - public void start() { - if (myCtx.getVersion().getVersion() == FhirVersionEnum.R4) { - Validate.notNull(myEventDefinitionDaoR4); - } - - if (getProcessingChannel() == null) { - myProcessingExecutorQueue = new LinkedBlockingQueue<>(1000); - RejectedExecutionHandler rejectedExecutionHandler = (theRunnable, theExecutor) -> { - ourLog.info("Note: Executor queue is full ({} elements), waiting for a slot to become available!", myProcessingExecutorQueue.size()); - StopWatch sw = new StopWatch(); - try { - myProcessingExecutorQueue.put(theRunnable); - } catch (InterruptedException theE) { - throw new RejectedExecutionException("Task " + theRunnable.toString() + - " rejected from " + theE.toString()); - } - ourLog.info("Slot become available after {}ms", sw.getMillis()); - }; - ThreadFactory threadFactory = new BasicThreadFactory.Builder() - .namingPattern("subscription-proc-%d") - .daemon(false) - .priority(Thread.NORM_PRIORITY) - .build(); - myProcessingExecutor = new ThreadPoolExecutor( - 1, - getExecutorThreadCount(), - 0L, - TimeUnit.MILLISECONDS, - myProcessingExecutorQueue, - threadFactory, - rejectedExecutionHandler); - setProcessingChannel(new ExecutorSubscribableChannel(myProcessingExecutor)); - } - - if (mySubscriptionActivatingSubscriber == null) { - IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao(); - mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(subscriptionDao, getChannelType(), this, myTxManager, myAsyncTaskExecutor); - } - - registerSubscriptionCheckingSubscriber(); - - TransactionTemplate transactionTemplate = new TransactionTemplate(myTxManager); - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - initSubscriptions(); - } - }); - } - - /** - * This is an internal API - Use with caution! - */ - public void submitResourceModified(final ResourceModifiedMessage theMsg) { - mySubscriptionActivatingSubscriber.handleMessage(theMsg.getOperationType(), theMsg.getId(myCtx), theMsg.getNewPayload(myCtx)); - sendToProcessingChannel(theMsg); - } - - private void unregisterAllSubscriptionsNotInCollection(Collection theAllIds) { - for (String next : new ArrayList<>(myIdToSubscription.keySet())) { - if (!theAllIds.contains(next)) { - ourLog.info("Unregistering Subscription/{}", next); - CanonicalSubscription subscription = myIdToSubscription.get(next); - unregisterSubscription(subscription.getIdElement(myCtx)); - } - } - } - - public void unregisterHandler(String theSubscriptionId, MessageHandler theMessageHandler) { - SubscribableChannel channel = mySubscribableChannel.get(theSubscriptionId); - if (channel != null) { - channel.unsubscribe(theMessageHandler); - if (channel instanceof DisposableBean) { - try { - ((DisposableBean) channel).destroy(); - } catch (Exception e) { - ourLog.error("Failed to destroy channel bean", e); - } - } - } - - mySubscribableChannel.remove(theSubscriptionId); - } - - @SuppressWarnings("UnusedReturnValue") - public CanonicalSubscription unregisterSubscription(IIdType theId) { - Validate.notNull(theId); - - String subscriptionId = theId.getIdPart(); - Validate.notBlank(subscriptionId); - - for (MessageHandler next : new ArrayList<>(myIdToDeliveryHandler.get(subscriptionId))) { - unregisterHandler(subscriptionId, next); - } - - mySubscribableChannel.remove(subscriptionId); - - return myIdToSubscription.remove(subscriptionId); - } - - public IFhirResourceDao getSubscriptionDao() { - return myDaoRegistry.getSubscriptionDao(); - } - - public IFhirResourceDao getDao(Class type) { - return myDaoRegistry.getResourceDao(type); - } - - public void setResourceDaos(List theResourceDaos) { - myDaoRegistry.setResourceDaos(theResourceDaos); - } - - public void validateCriteria(final S theResource) { - CanonicalSubscription subscription = canonicalize(theResource); - String criteria = subscription.getCriteriaString(); - try { - RuntimeResourceDefinition resourceDef = CacheWarmingSvcImpl.parseUrlResourceType(myCtx, criteria); - myMatchUrlService.translateMatchUrl(criteria, resourceDef); - } catch (InvalidRequestException e) { - throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + criteria + " " + e.getMessage()); - } - } - - @VisibleForTesting - public static void setForcePayloadEncodeAndDecodeForUnitTests(boolean theForcePayloadEncodeAndDecodeForUnitTests) { - ourForcePayloadEncodeAndDecodeForUnitTests = theForcePayloadEncodeAndDecodeForUnitTests; - } -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionSubscriber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionSubscriber.java deleted file mode 100644 index 3a7d3fff7fc..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionSubscriber.java +++ /dev/null @@ -1,98 +0,0 @@ -package ca.uhn.fhir.jpa.subscription; - -/*- - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2018 University Health Network - * %% - * 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% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; -import org.hl7.fhir.r4.model.Subscription; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.messaging.MessageHandler; - -import javax.annotation.PostConstruct; - -public abstract class BaseSubscriptionSubscriber implements MessageHandler { - - private final Subscription.SubscriptionChannelType myChannelType; - private final BaseSubscriptionInterceptor mySubscriptionInterceptor; - @Autowired - DaoRegistry myDaoRegistry; - private IFhirResourceDao mySubscriptionDao; - - /** - * Constructor - */ - public BaseSubscriptionSubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) { - myChannelType = theChannelType; - mySubscriptionInterceptor = theSubscriptionInterceptor; - } - - @SuppressWarnings("unused") // Don't delete, used in Smile - public void setDaoRegistry(DaoRegistry theDaoRegistry) { - myDaoRegistry = theDaoRegistry; - } - - @PostConstruct - public void setSubscriptionDao() { - mySubscriptionDao = myDaoRegistry.getSubscriptionDao(); - } - - public Subscription.SubscriptionChannelType getChannelType() { - return myChannelType; - } - - public FhirContext getContext() { - return getSubscriptionDao().getContext(); - } - - public IFhirResourceDao getSubscriptionDao() { - return mySubscriptionDao; - } - - public BaseSubscriptionInterceptor getSubscriptionInterceptor() { - return mySubscriptionInterceptor; - } - - - /** - * Does this subscription type (e.g. rest hook, websocket, etc) apply to this interceptor? - */ - protected boolean subscriptionTypeApplies(CanonicalSubscription theSubscription) { - Subscription.SubscriptionChannelType channelType = getChannelType(); - String subscriptionType = theSubscription.getChannelType().toCode(); - return subscriptionTypeApplies(subscriptionType, channelType); - } - - /** - * Does this subscription type (e.g. rest hook, websocket, etc) apply to this interceptor? - */ - static boolean subscriptionTypeApplies(String theSubscriptionChannelTypeCode, Subscription.SubscriptionChannelType theChannelType) { - boolean subscriptionTypeApplies = false; - if (theSubscriptionChannelTypeCode != null) { - if (theChannelType.toCode().equals(theSubscriptionChannelTypeCode)) { - subscriptionTypeApplies = true; - } - } - return subscriptionTypeApplies; - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/DaoResourceRetriever.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/DaoResourceRetriever.java new file mode 100644 index 00000000000..fdfa292ca9e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/DaoResourceRetriever.java @@ -0,0 +1,32 @@ +package ca.uhn.fhir.jpa.subscription; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.module.subscriber.IResourceRetriever; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class DaoResourceRetriever implements IResourceRetriever { + private static final Logger ourLog = LoggerFactory.getLogger(ActiveSubscription.class); + + @Autowired + FhirContext myFhirContext; + @Autowired + DaoRegistry myDaoRegistry; + + @Override + public IBaseResource getResource(IIdType payloadId) throws ResourceGoneException { + RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(payloadId.getResourceType()); + IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceDef.getImplementingClass()); + return dao.read(payloadId.toVersionless()); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java new file mode 100644 index 00000000000..6563d8bbd31 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java @@ -0,0 +1,247 @@ +package ca.uhn.fhir.jpa.subscription; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2018 University Health Network + * %% + * 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% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jpa.config.BaseConfig; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl; +import ca.uhn.fhir.jpa.searchparam.MatchUrlService; +import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionCannonicalizer; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; +import ca.uhn.fhir.util.SubscriptionUtil; +import com.google.common.annotations.VisibleForTesting; +import org.hl7.fhir.instance.model.Subscription; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionSynchronizationAdapter; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.transaction.support.TransactionTemplate; + +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * Responsible for transitioning subscription resources from REQUESTED to ACTIVE + * Once activated, the subscription is added to the SubscriptionRegistry. + * + * Also validates criteria. If invalid, rejects the subscription without persisting the subscription. + */ +@Service +public class SubscriptionActivatingInterceptor extends ServerOperationInterceptorAdapter { + private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingInterceptor.class); + + private static boolean ourWaitForSubscriptionActivationSynchronouslyForUnitTest; + + @Autowired + private PlatformTransactionManager myTransactionManager; + @Autowired + @Qualifier(BaseConfig.TASK_EXECUTOR_NAME) + private AsyncTaskExecutor myTaskExecutor; + @Autowired + private SubscriptionRegistry mySubscriptionRegistry; + @Autowired + private DaoRegistry myDaoRegistry; + @Autowired + private FhirContext myFhirContext; + @Autowired + private SubscriptionCannonicalizer mySubscriptionCannonicalizer; + @Autowired + private MatchUrlService myMatchUrlService; + @Autowired + private DaoConfig myDaoConfig; + + public boolean activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) { + // Grab the value for "Subscription.channel.type" so we can see if this + // subscriber applies.. + String subscriptionChannelTypeCode = myFhirContext + .newTerser() + .getSingleValueOrNull(theSubscription, SubscriptionMatcherInterceptor.SUBSCRIPTION_TYPE, IPrimitiveType.class) + .getValueAsString(); + + Subscription.SubscriptionChannelType subscriptionChannelType = Subscription.SubscriptionChannelType.fromCode(subscriptionChannelTypeCode); + // Only activate supported subscriptions + if (!myDaoConfig.getSupportedSubscriptionTypes().contains(subscriptionChannelType)) { + return false; + } + + final IPrimitiveType status = myFhirContext.newTerser().getSingleValueOrNull(theSubscription, SubscriptionMatcherInterceptor.SUBSCRIPTION_STATUS, IPrimitiveType.class); + String statusString = status.getValueAsString(); + + final String requestedStatus = Subscription.SubscriptionStatus.REQUESTED.toCode(); + final String activeStatus = Subscription.SubscriptionStatus.ACTIVE.toCode(); + if (requestedStatus.equals(statusString)) { + if (TransactionSynchronizationManager.isSynchronizationActive()) { + /* + * If we're in a transaction, we don't want to try and change the status from + * requested to active within the same transaction because it's too late by + * the time we get here to make modifications to the payload. + * + * So, we register a synchronization, meaning that when the transaction is + * finished, we'll schedule a task to do this in a separate worker thread + * to avoid any possibility of conflict. + */ + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { + @Override + public void afterCommit() { + Future activationFuture = myTaskExecutor.submit(new Runnable() { + @Override + public void run() { + activateSubscription(activeStatus, theSubscription, requestedStatus); + } + }); + + /* + * If we're running in a unit test, it's nice to be predictable in + * terms of order... In the real world it's a recipe for deadlocks + */ + if (ourWaitForSubscriptionActivationSynchronouslyForUnitTest) { + try { + activationFuture.get(5, TimeUnit.SECONDS); + } catch (Exception e) { + ourLog.error("Failed to activate subscription", e); + } + } + } + }); + return true; + } else { + return activateSubscription(activeStatus, theSubscription, requestedStatus); + } + } else if (activeStatus.equals(statusString)) { + return mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(theSubscription); + } else { + // Status isn't "active" or "requested" + return mySubscriptionRegistry.unregisterSubscriptionIfRegistered(theSubscription, statusString); + } + } + + + private boolean activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) { + IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao(); + IBaseResource subscription = subscriptionDao.read(theSubscription.getIdElement()); + + ourLog.info("Activating subscription {} from status {} to {}", subscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus); + try { + SubscriptionUtil.setStatus(myFhirContext, subscription, theActiveStatus); + subscription = subscriptionDao.update(subscription).getResource(); + submitResourceModifiedForUpdate(subscription); + return true; + } catch (final UnprocessableEntityException e) { + ourLog.info("Changing status of {} to ERROR", subscription.getIdElement()); + SubscriptionUtil.setStatus(myFhirContext, subscription, "error"); + SubscriptionUtil.setReason(myFhirContext, subscription, e.getMessage()); + subscriptionDao.update(subscription); + return false; + } + } + + void submitResourceModifiedForUpdate(IBaseResource theNewResource) { + submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE); + } + + @Override + public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) { + submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE); + } + + @Override + public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) { + submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE); + } + + @Override + public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) { + submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE); + } + + private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) { + submitResourceModified(new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType)); + } + + private void submitResourceModified(final ResourceModifiedMessage theMsg) { + IIdType id = theMsg.getId(myFhirContext); + if (!id.getResourceType().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { + return; + } + switch (theMsg.getOperationType()) { + case DELETE: + mySubscriptionRegistry.unregisterSubscription(id); + break; + case CREATE: + case UPDATE: + final IBaseResource subscription = theMsg.getNewPayload(myFhirContext); + validateCriteria(subscription); + activateAndRegisterSubscriptionIfRequiredInTransaction(subscription); + break; + default: + break; + } + } + + public void validateCriteria(final IBaseResource theResource) { + CanonicalSubscription subscription = mySubscriptionCannonicalizer.canonicalize(theResource); + String criteria = subscription.getCriteriaString(); + try { + RuntimeResourceDefinition resourceDef = CacheWarmingSvcImpl.parseUrlResourceType(myFhirContext, criteria); + myMatchUrlService.translateMatchUrl(criteria, resourceDef); + } catch (InvalidRequestException e) { + throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + criteria + " " + e.getMessage()); + } + } + + private void activateAndRegisterSubscriptionIfRequiredInTransaction(IBaseResource theSubscription) { + TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); + txTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + activateOrRegisterSubscriptionIfRequired(theSubscription); + } + }); + } + + + @VisibleForTesting + public static void setWaitForSubscriptionActivationSynchronouslyForUnitTest(boolean theWaitForSubscriptionActivationSynchronouslyForUnitTest) { + ourWaitForSubscriptionActivationSynchronouslyForUnitTest = theWaitForSubscriptionActivationSynchronouslyForUnitTest; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingSubscriber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingSubscriber.java deleted file mode 100644 index 44f488b9c54..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingSubscriber.java +++ /dev/null @@ -1,220 +0,0 @@ -package ca.uhn.fhir.jpa.subscription; - -/*- - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2018 University Health Network - * %% - * 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% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; -import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import ca.uhn.fhir.util.SubscriptionUtil; -import com.google.common.annotations.VisibleForTesting; -import org.apache.commons.lang3.Validate; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r4.model.Subscription; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.core.task.AsyncTaskExecutor; -import org.springframework.messaging.MessagingException; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; -import org.springframework.transaction.support.TransactionSynchronizationAdapter; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.transaction.support.TransactionTemplate; - -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -@SuppressWarnings("unchecked") -public class SubscriptionActivatingSubscriber { - private static boolean ourWaitForSubscriptionActivationSynchronouslyForUnitTest; - private final IFhirResourceDao mySubscriptionDao; - private final BaseSubscriptionInterceptor mySubscriptionInterceptor; - private final PlatformTransactionManager myTransactionManager; - private final AsyncTaskExecutor myTaskExecutor; - private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingSubscriber.class); - private FhirContext myCtx; - private Subscription.SubscriptionChannelType myChannelType; - - - /** - * Constructor - */ - public SubscriptionActivatingSubscriber(IFhirResourceDao theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, PlatformTransactionManager theTransactionManager, AsyncTaskExecutor theTaskExecutor) { - mySubscriptionDao = theSubscriptionDao; - mySubscriptionInterceptor = theSubscriptionInterceptor; - myChannelType = theChannelType; - myCtx = theSubscriptionDao.getContext(); - myTransactionManager = theTransactionManager; - myTaskExecutor = theTaskExecutor; - Validate.notNull(theTaskExecutor); - } - - public boolean activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) { - // Grab the value for "Subscription.channel.type" so we can see if this - // subscriber applies.. - String subscriptionChannelType = myCtx - .newTerser() - .getSingleValueOrNull(theSubscription, BaseSubscriptionInterceptor.SUBSCRIPTION_TYPE, IPrimitiveType.class) - .getValueAsString(); - boolean subscriptionTypeApplies = BaseSubscriptionSubscriber.subscriptionTypeApplies(subscriptionChannelType, myChannelType); - if (subscriptionTypeApplies == false) { - return false; - } - - final IPrimitiveType status = myCtx.newTerser().getSingleValueOrNull(theSubscription, BaseSubscriptionInterceptor.SUBSCRIPTION_STATUS, IPrimitiveType.class); - String statusString = status.getValueAsString(); - - final String requestedStatus = Subscription.SubscriptionStatus.REQUESTED.toCode(); - final String activeStatus = Subscription.SubscriptionStatus.ACTIVE.toCode(); - if (requestedStatus.equals(statusString)) { - if (TransactionSynchronizationManager.isSynchronizationActive()) { - /* - * If we're in a transaction, we don't want to try and change the status from - * requested to active within the same transaction because it's too late by - * the time we get here to make modifications to the payload. - * - * So, we register a synchronization, meaning that when the transaction is - * finished, we'll schedule a task to do this in a separate worker thread - * to avoid any possibility of conflict. - */ - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { - @Override - public void afterCommit() { - Future activationFuture = myTaskExecutor.submit(new Runnable() { - @Override - public void run() { - activateSubscription(activeStatus, theSubscription, requestedStatus); - } - }); - - /* - * If we're running in a unit test, it's nice to be predictable in - * terms of order... In the real world it's a recipe for deadlocks - */ - if (ourWaitForSubscriptionActivationSynchronouslyForUnitTest) { - try { - activationFuture.get(5, TimeUnit.SECONDS); - } catch (Exception e) { - ourLog.error("Failed to activate subscription", e); - } - } - } - }); - return true; - } else { - return activateSubscription(activeStatus, theSubscription, requestedStatus); - } - } else if (activeStatus.equals(statusString)) { - return registerSubscriptionUnlessAlreadyRegistered(theSubscription); - } else { - // Status isn't "active" or "requested" - return unregisterSubscriptionIfRegistered(theSubscription, statusString); - } - } - - protected boolean unregisterSubscriptionIfRegistered(IBaseResource theSubscription, String theStatusString) { - if (mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement()) != null) { - ourLog.info("Removing {} subscription {}", theStatusString, theSubscription.getIdElement().toUnqualified().getValue()); - mySubscriptionInterceptor.unregisterSubscription(theSubscription.getIdElement()); - return true; - } - return false; - } - - private boolean activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) { - IBaseResource subscription = mySubscriptionDao.read(theSubscription.getIdElement()); - - ourLog.info("Activating subscription {} from status {} to {} for channel {}", subscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus, myChannelType); - try { - SubscriptionUtil.setStatus(myCtx, subscription, theActiveStatus); - subscription = mySubscriptionDao.update(subscription).getResource(); - mySubscriptionInterceptor.submitResourceModifiedForUpdate(subscription); - return true; - } catch (final UnprocessableEntityException e) { - ourLog.info("Changing status of {} to ERROR", subscription.getIdElement()); - SubscriptionUtil.setStatus(myCtx, subscription, "error"); - SubscriptionUtil.setReason(myCtx, subscription, e.getMessage()); - mySubscriptionDao.update(subscription); - return false; - } - - } - - @SuppressWarnings("EnumSwitchStatementWhichMissesCases") - public void handleMessage(ResourceModifiedMessage.OperationTypeEnum theOperationType, IIdType theId, final IBaseResource theSubscription) throws MessagingException { - if (!theId.getResourceType().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { - return; - } - switch (theOperationType) { - case DELETE: - mySubscriptionInterceptor.unregisterSubscription(theId); - break; - case CREATE: - case UPDATE: - mySubscriptionInterceptor.validateCriteria(theSubscription); - activateAndRegisterSubscriptionIfRequiredInTransaction(theSubscription); - break; - default: - break; - } - - } - - private void activateAndRegisterSubscriptionIfRequiredInTransaction(IBaseResource theSubscription) { - TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); - txTemplate.execute(new TransactionCallbackWithoutResult() { - @Override - protected void doInTransactionWithoutResult(TransactionStatus status) { - activateOrRegisterSubscriptionIfRequired(theSubscription); - } - }); - } - - protected synchronized boolean registerSubscriptionUnlessAlreadyRegistered(IBaseResource theSubscription) { - CanonicalSubscription existingSubscription = mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement()); - CanonicalSubscription newSubscription = mySubscriptionInterceptor.canonicalize(theSubscription); - - if (existingSubscription != null) { - if (newSubscription.equals(existingSubscription)) { - // No changes - return false; - } - } - - if (existingSubscription != null) { - ourLog.info("Updating already-registered active subscription {}", theSubscription.getIdElement().toUnqualified().getValue()); - mySubscriptionInterceptor.unregisterSubscription(theSubscription.getIdElement()); - } else { - ourLog.info("Registering active subscription {}", theSubscription.getIdElement().toUnqualified().getValue()); - } - mySubscriptionInterceptor.registerSubscription(theSubscription.getIdElement(), theSubscription); - return true; - } - - @VisibleForTesting - public static void setWaitForSubscriptionActivationSynchronouslyForUnitTest(boolean theWaitForSubscriptionActivationSynchronouslyForUnitTest) { - ourWaitForSubscriptionActivationSynchronouslyForUnitTest = theWaitForSubscriptionActivationSynchronouslyForUnitTest; - } - -} 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 new file mode 100644 index 00000000000..0d3890b04ff --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionInterceptorLoader.java @@ -0,0 +1,34 @@ +package ca.uhn.fhir.jpa.subscription; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import com.google.common.annotations.VisibleForTesting; +import org.hl7.fhir.instance.model.Subscription; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Set; + +@Service +public class SubscriptionInterceptorLoader { + @Autowired + DaoConfig myDaoConfig; + @Autowired + SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor; + @Autowired + SubscriptionActivatingInterceptor mySubscriptionActivatingInterceptor; + + public void registerInterceptors() { + Set supportedSubscriptionTypes = myDaoConfig.getSupportedSubscriptionTypes(); + + if (!supportedSubscriptionTypes.isEmpty()) { + myDaoConfig.registerInterceptor(mySubscriptionActivatingInterceptor); + myDaoConfig.registerInterceptor(mySubscriptionMatcherInterceptor); + } + } + + @VisibleForTesting + public void unregisterInterceptorsForUnitTest() { + myDaoConfig.unregisterInterceptor(mySubscriptionActivatingInterceptor); + myDaoConfig.unregisterInterceptor(mySubscriptionMatcherInterceptor); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java new file mode 100644 index 00000000000..796f9af86bd --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java @@ -0,0 +1,127 @@ +package ca.uhn.fhir.jpa.subscription; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.SubscriptionChannel; +import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory; +import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage; +import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionCheckingSubscriber; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; +import com.google.common.annotations.VisibleForTesting; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.SubscribableChannel; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2018 University Health Network + * %% + * 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% + */ + +@Component +public class SubscriptionMatcherInterceptor extends ServerOperationInterceptorAdapter { + private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherInterceptor.class); + + static final String SUBSCRIPTION_STATUS = "Subscription.status"; + static final String SUBSCRIPTION_TYPE = "Subscription.channel.type"; + private static boolean ourForcePayloadEncodeAndDecodeForUnitTests; + private SubscribableChannel myProcessingChannel; + + @Autowired + private FhirContext myFhirContext; + @Autowired + private SubscriptionCheckingSubscriber mySubscriptionCheckingSubscriber; + @Autowired + private ISubscriptionChannelFactory mySubscriptionChannelFactory; + + /** + * Constructor + */ + public SubscriptionMatcherInterceptor() { + super(); + } + + @PostConstruct + public void start() { + if (myProcessingChannel == null) { + myProcessingChannel = mySubscriptionChannelFactory.newMatchingChannel("subscription-matching"); + } + myProcessingChannel.subscribe(mySubscriptionCheckingSubscriber); + } + + @SuppressWarnings("unused") + @PreDestroy + public void preDestroy() { + myProcessingChannel.unsubscribe(mySubscriptionCheckingSubscriber); + } + + @Override + public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) { + submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE); + } + + @Override + public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) { + submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE); + } + + @Override + public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) { + submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE); + } + + private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) { + ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType); + if (ourForcePayloadEncodeAndDecodeForUnitTests) { + msg.clearPayloadDecoded(); + } + submitResourceModified(msg); + } + + protected void sendToProcessingChannel(final ResourceModifiedMessage theMessage) { + ourLog.trace("Sending resource modified message to processing channel"); + myProcessingChannel.send(new ResourceModifiedJsonMessage(theMessage)); + } + + public void setFhirContext(FhirContext theCtx) { + myFhirContext = theCtx; + } + + /** + * This is an internal API - Use with caution! + */ + public void submitResourceModified(final ResourceModifiedMessage theMsg) { + sendToProcessingChannel(theMsg); + } + + @VisibleForTesting + public static void setForcePayloadEncodeAndDecodeForUnitTests(boolean theForcePayloadEncodeAndDecodeForUnitTests) { + ourForcePayloadEncodeAndDecodeForUnitTests = theForcePayloadEncodeAndDecodeForUnitTests; + } + + @VisibleForTesting + public SubscriptionChannel getProcessingChannelForUnitTest() { + return (SubscriptionChannel) myProcessingChannel; + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java index 96bd3f9c032..01893c8a7e0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java @@ -22,13 +22,16 @@ package ca.uhn.fhir.jpa.subscription; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.searchparam.MatchUrlService; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl; +import ca.uhn.fhir.jpa.searchparam.MatchUrlService; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.api.CacheControlDirective; @@ -38,6 +41,7 @@ import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.ValidateUtil; @@ -71,26 +75,31 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; @Service public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc, ApplicationContextAware { + private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTriggeringProvider.class); public static final int DEFAULT_MAX_SUBMIT = 10000; - private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTriggeringProvider.class); - private final List myActiveJobs = new ArrayList<>(); + @Autowired private FhirContext myFhirContext; @Autowired private DaoRegistry myDaoRegistry; - private List> mySubscriptionInterceptorList; - private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT; + @Autowired + private DaoConfig myDaoConfig; @Autowired private ISearchCoordinatorSvc mySearchCoordinatorSvc; @Autowired private MatchUrlService myMatchUrlService; + @Autowired + private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor; + + private final List myActiveJobs = new ArrayList<>(); + private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT; private ApplicationContext myAppCtx; private ExecutorService myExecutorService; @Override public IBaseParameters triggerSubscription(List theResourceIds, List theSearchUrls, @IdParam IIdType theSubscriptionId) { - if (mySubscriptionInterceptorList.isEmpty()) { + if (myDaoConfig.getSupportedSubscriptionTypes().isEmpty()) { throw new PreconditionFailedException("Subscription processing not active on this server"); } @@ -293,18 +302,13 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc ourLog.info("Submitting resource {} to subscription {}", theResourceToTrigger.getIdElement().toUnqualifiedVersionless().getValue(), theSubscriptionId); - ResourceModifiedMessage msg = new ResourceModifiedMessage(); - msg.setId(theResourceToTrigger.getIdElement()); - msg.setOperationType(ResourceModifiedMessage.OperationTypeEnum.UPDATE); + ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, theResourceToTrigger, ResourceModifiedMessage.OperationTypeEnum.UPDATE); msg.setSubscriptionId(new IdType(theSubscriptionId).toUnqualifiedVersionless().getValue()); - msg.setNewPayload(myFhirContext, theResourceToTrigger); return myExecutorService.submit(() -> { for (int i = 0; ; i++) { try { - for (BaseSubscriptionInterceptor next : mySubscriptionInterceptorList) { - next.submitResourceModified(msg); - } + mySubscriptionMatcherInterceptor.submitResourceModified(msg); break; } catch (Exception e) { if (i >= 3) { @@ -347,13 +351,6 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc @SuppressWarnings("unchecked") @PostConstruct public void start() { - mySubscriptionInterceptorList = ObjectUtils.defaultIfNull(mySubscriptionInterceptorList, Collections.emptyList()); - mySubscriptionInterceptorList = new ArrayList<>(); - Collection values1 = myAppCtx.getBeansOfType(BaseSubscriptionInterceptor.class).values(); - Collection> values = (Collection>) values1; - mySubscriptionInterceptorList.addAll(values); - - LinkedBlockingQueue executorQueue = new LinkedBlockingQueue<>(1000); BasicThreadFactory threadFactory = new BasicThreadFactory.Builder() .namingPattern("SubscriptionTriggering-%d") diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbcache/DaoSubscriptionProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbcache/DaoSubscriptionProvider.java new file mode 100644 index 00000000000..ba416204b4e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbcache/DaoSubscriptionProvider.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.jpa.subscription.dbcache; + +import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; +import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class DaoSubscriptionProvider implements ISubscriptionProvider { + @Autowired + DaoRegistry myDaoRegistry; + @Autowired + private SubscriptionActivatingInterceptor mySubscriptionActivatingInterceptor; + + @Override + public IBundleProvider search(SearchParameterMap theMap) { + IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao(); + RequestDetails req = new ServletSubRequestDetails(); + req.setSubRequest(true); + + return subscriptionDao.search(theMap, req); + } + + @Override + public boolean loadSubscription(IBaseResource theResource) { + return mySubscriptionActivatingInterceptor.activateOrRegisterSubscriptionIfRequired(theResource); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherCompositeInMemoryDatabase.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java similarity index 52% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherCompositeInMemoryDatabase.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java index 788aa9ef139..84549dd1dbd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherCompositeInMemoryDatabase.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.matcher; +package ca.uhn.fhir.jpa.subscription.dbmatcher; /*- * #%L @@ -21,34 +21,38 @@ package ca.uhn.fhir.jpa.subscription.matcher; */ import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult; +import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -@Service -public class SubscriptionMatcherCompositeInMemoryDatabase implements ISubscriptionMatcher { - private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherCompositeInMemoryDatabase.class); +public class CompositeInMemoryDaoSubscriptionMatcher implements ISubscriptionMatcher { + private Logger ourLog = LoggerFactory.getLogger(CompositeInMemoryDaoSubscriptionMatcher.class); - @Autowired - SubscriptionMatcherDatabase mySubscriptionMatcherDatabase; - @Autowired - SubscriptionMatcherInMemory mySubscriptionMatcherInMemory; + private final DaoSubscriptionMatcher myDaoSubscriptionMatcher; + private final InMemorySubscriptionMatcher myInMemorySubscriptionMatcher; @Autowired DaoConfig myDaoConfig; + public CompositeInMemoryDaoSubscriptionMatcher(DaoSubscriptionMatcher theDaoSubscriptionMatcher, InMemorySubscriptionMatcher theInMemorySubscriptionMatcher) { + myDaoSubscriptionMatcher = theDaoSubscriptionMatcher; + myInMemorySubscriptionMatcher = theInMemorySubscriptionMatcher; + } + @Override public SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg) { SubscriptionMatchResult result; if (myDaoConfig.isEnableInMemorySubscriptionMatching()) { - result = mySubscriptionMatcherInMemory.match(criteria, msg); + result = myInMemorySubscriptionMatcher.match(criteria, msg); if (!result.supported()) { ourLog.info("Criteria {} not supported by InMemoryMatcher: {}. Reverting to DatabaseMatcher", criteria, result.getUnsupportedReason()); - result = mySubscriptionMatcherDatabase.match(criteria, msg); + result = myDaoSubscriptionMatcher.match(criteria, msg); } } else { - result = mySubscriptionMatcherDatabase.match(criteria, msg); + result = myDaoSubscriptionMatcher.match(criteria, msg); } return result; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherDatabase.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java similarity index 87% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherDatabase.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java index 4614fde8207..6b1fbad53f0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherDatabase.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.matcher; +package ca.uhn.fhir.jpa.subscription.dbmatcher; /*- * #%L @@ -24,10 +24,12 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; -import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -35,13 +37,9 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -@Service -@Lazy -public class SubscriptionMatcherDatabase implements ISubscriptionMatcher { - private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherDatabase.class); +public class DaoSubscriptionMatcher implements ISubscriptionMatcher { + private Logger ourLog = LoggerFactory.getLogger(DaoSubscriptionMatcher.class); @Autowired private FhirContext myCtx; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionEmailInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionEmailInterceptor.java deleted file mode 100644 index 0e3c85db169..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionEmailInterceptor.java +++ /dev/null @@ -1,89 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.email; - -/*- - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2018 University Health Network - * %% - * 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% - */ - -import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor; -import ca.uhn.fhir.jpa.subscription.CanonicalSubscription; -import org.apache.commons.lang3.Validate; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.messaging.MessageHandler; -import org.springframework.stereotype.Component; - -import java.util.Optional; - -/** - * Note: If you're going to use this, you need to provide a bean - * of type {@link ca.uhn.fhir.jpa.subscription.email.IEmailSender} - * in your own Spring config - */ -public class SubscriptionEmailInterceptor extends BaseSubscriptionInterceptor { - - /** - * This is set to autowired=false just so that implementors can supply this - * with a mechanism other than autowiring if they want - */ - @Autowired(required = false) - private IEmailSender myEmailSender; - @Autowired - BeanFactory myBeanFactory; - private String myDefaultFromAddress = "noreply@unknown.com"; - - @Override - protected Optional createDeliveryHandler(CanonicalSubscription theSubscription) { - return Optional.of(myBeanFactory.getBean(SubscriptionDeliveringEmailSubscriber.class, getChannelType(), this)); - } - - @Override - public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() { - return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.EMAIL; - } - - /** - * The "from" address to use for any sent emails that to not explicitly specity a from address - */ - public String getDefaultFromAddress() { - return myDefaultFromAddress; - } - - /** - * The "from" address to use for any sent emails that to not explicitly specity a from address - */ - public void setDefaultFromAddress(String theDefaultFromAddress) { - Validate.notBlank(theDefaultFromAddress, "theDefaultFromAddress must not be null or blank"); - myDefaultFromAddress = theDefaultFromAddress; - } - - public IEmailSender getEmailSender() { - return myEmailSender; - } - - /** - * Set the email sender (this method does not need to be explicitly called if you - * are using autowiring to supply the sender) - */ - public void setEmailSender(IEmailSender theEmailSender) { - myEmailSender = theEmailSender; - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java~HEAD b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java~HEAD deleted file mode 100644 index 22e4943bdad..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java~HEAD +++ /dev/null @@ -1,7 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.matcher; - -import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage; - -public interface ISubscriptionMatcher { - boolean match(String criteria, ResourceModifiedMessage msg); -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java deleted file mode 100644 index 06668501498..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java +++ /dev/null @@ -1,46 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.resthook; - -/*- - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2018 University Health Network - * %% - * 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% - */ - -import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor; -import ca.uhn.fhir.jpa.subscription.CanonicalSubscription; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.messaging.MessageHandler; - -import java.util.Optional; - -public class SubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor { - @Autowired - BeanFactory myBeanFactory; - - @Override - protected Optional createDeliveryHandler(CanonicalSubscription theSubscription) { - SubscriptionDeliveringRestHookSubscriber value = myBeanFactory.getBean(SubscriptionDeliveringRestHookSubscriber.class, getChannelType(), this); - return Optional.of(value); - } - - @Override - public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() { - return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK; - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/ISubscriptionWebsocketHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/ISubscriptionWebsocketHandler.java deleted file mode 100644 index 81af49b16ed..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/ISubscriptionWebsocketHandler.java +++ /dev/null @@ -1,27 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.websocket; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2018 University Health Network - * %% - * 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% - */ - -import org.springframework.web.socket.WebSocketHandler; - -public interface ISubscriptionWebsocketHandler extends WebSocketHandler { - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketInterceptor.java deleted file mode 100644 index 9eb56024454..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketInterceptor.java +++ /dev/null @@ -1,43 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.websocket; - -/*- - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2018 University Health Network - * %% - * 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% - */ - -import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor; -import ca.uhn.fhir.jpa.subscription.CanonicalSubscription; -import org.hl7.fhir.r4.model.Subscription; -import org.springframework.messaging.MessageHandler; - -import java.util.Optional; - -public class SubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor { - - @Override - protected Optional createDeliveryHandler(CanonicalSubscription theSubscription) { - return Optional.empty(); - } - - @Override - public Subscription.SubscriptionChannelType getChannelType() { - return Subscription.SubscriptionChannelType.WEBSOCKET; - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java index f155a961edf..87a617aad51 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java @@ -23,51 +23,6 @@ package ca.uhn.fhir.jpa.util; import ca.uhn.fhir.rest.api.Constants; public class JpaConstants { - - /** - *

- * This extension should be of type string and should be - * placed on the Subscription.channel element - *

- */ - public static final String EXT_SUBSCRIPTION_EMAIL_FROM = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-from"; - - /** - *

- * This extension should be of type string and should be - * placed on the Subscription.channel element - *

- */ - public static final String EXT_SUBSCRIPTION_SUBJECT_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-subject-template"; - - - /** - * This extension URL indicates whether a REST HOOK delivery should - * include the version ID when delivering. - *

- * This extension should be of type boolean and should be - * placed on the Subscription.channel element. - *

- */ - public static final String EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids"; - - /** - * This extension URL indicates whether a REST HOOK delivery should - * reload the resource and deliver the latest version always. This - * could be useful for example if a resource which triggers a - * subscription gets updated many times in short succession and there - * is no value in delivering the older versions. - *

- * Note that if the resource is now deleted, this may cause - * the delivery to be cancelled altogether. - *

- * - *

- * This extension should be of type boolean and should be - * placed on the Subscription.channel element. - *

- */ - public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version"; /** * Operation name for the $expunge operation */ diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/StoppableSubscriptionDeliveringRestHookSubscriber.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/StoppableSubscriptionDeliveringRestHookSubscriber.java new file mode 100644 index 00000000000..d4603c46202 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/StoppableSubscriptionDeliveringRestHookSubscriber.java @@ -0,0 +1,50 @@ +package ca.uhn.fhir.jpa.config; + +import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionDeliveringRestHookSubscriber; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessagingException; + +import java.util.concurrent.CountDownLatch; + +public class StoppableSubscriptionDeliveringRestHookSubscriber extends SubscriptionDeliveringRestHookSubscriber { + private static final Logger ourLog = LoggerFactory.getLogger(StoppableSubscriptionDeliveringRestHookSubscriber.class); + + private boolean myPauseEveryMessage = false; + private CountDownLatch myCountDownLatch; + + @Override + public void handleMessage(Message theMessage) throws MessagingException { + if (myCountDownLatch != null) { + myCountDownLatch.countDown(); + } + if (myPauseEveryMessage) { + waitIfPaused(); + } + super.handleMessage(theMessage); + } + + private synchronized void waitIfPaused() { + try { + if (myPauseEveryMessage) { + wait(); + } + } catch (InterruptedException theE) { + ourLog.error("interrupted", theE); + } + } + + public void pause() { + myPauseEveryMessage = true; + } + + public synchronized void unPause() { + myPauseEveryMessage = false; + notifyAll(); + } + + public void setCountDownLatch(CountDownLatch theCountDownLatch) { + myCountDownLatch = theCountDownLatch; + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java index 545962d4246..ed7243b9b9e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java @@ -1,28 +1,23 @@ package ca.uhn.fhir.jpa.config; -import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; -import ca.uhn.fhir.jpa.subscription.email.IEmailSender; -import ca.uhn.fhir.jpa.subscription.email.JavaMailEmailSender; +import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender; +import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import org.apache.commons.dbcp2.BasicDataSource; -import org.hibernate.jpa.HibernatePersistenceProvider; import org.springframework.context.annotation.*; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; -import org.springframework.core.env.Environment; -import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; -import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import java.sql.Connection; import java.util.Properties; import java.util.concurrent.TimeUnit; -import static org.junit.Assert.*; +import static org.junit.Assert.fail; @Configuration @Import(TestJPAConfig.class) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestJPAConfig.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestJPAConfig.java index dfceb79fc87..7e9574c71ff 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestJPAConfig.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestJPAConfig.java @@ -2,12 +2,12 @@ package ca.uhn.fhir.jpa.config; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; +import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionDeliveringRestHookSubscriber; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import org.springframework.orm.jpa.JpaTransactionManager; @@ -38,4 +38,16 @@ public class TestJPAConfig { public UnregisterScheduledProcessor unregisterScheduledProcessor(Environment theEnv) { return new UnregisterScheduledProcessor(theEnv); } + + @Lazy + @Bean + public SubscriptionTestUtil subscriptionTestUtil() { + return new SubscriptionTestUtil(); + } + + @Bean + @Primary + public SubscriptionDeliveringRestHookSubscriber stoppableSubscriptionDeliveringRestHookSubscriber() { + return new StoppableSubscriptionDeliveringRestHookSubscriber(); + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index d92b393293d..e24e7178622 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -1,27 +1,21 @@ package ca.uhn.fhir.jpa.config; -import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; import net.ttddyy.dsproxy.listener.SingleQueryCountHolder; -import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Lazy; -import org.springframework.core.env.Environment; -import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; -import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; -import java.util.concurrent.TimeUnit; import static org.junit.Assert.fail; @@ -102,8 +96,8 @@ public class TestR4Config extends BaseJavaConfigR4 { DataSource dataSource = ProxyDataSourceBuilder .create(retVal) - .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") - .logSlowQueryBySlf4j(10, TimeUnit.SECONDS) +// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") +// .logSlowQueryBySlf4j(10, TimeUnit.SECONDS) // .countQuery(new ThreadQueryCountHolder()) .countQuery(singleQueryCountHolder()) .build(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java index aa28e56d77d..d08e7b3b9cc 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java @@ -16,6 +16,7 @@ import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; @@ -181,6 +182,8 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest { protected IFhirResourceDaoValueSet myValueSetDao; @Autowired private ISearchParamRegistry mySearchParamRegistry; + @Autowired + protected SubscriptionLoader mySubscriptionLoader; @Before public void beforeCreateInterceptor() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3InvalidSubscriptionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3InvalidSubscriptionTest.java index b958190e5f6..73b444b906e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3InvalidSubscriptionTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3InvalidSubscriptionTest.java @@ -2,8 +2,7 @@ package ca.uhn.fhir.jpa.dao.dstu3; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingSubscriber; -import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; +import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.dstu3.model.Subscription; @@ -23,19 +22,20 @@ import static org.junit.Assert.*; public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Test { + @Autowired - private SubscriptionRestHookInterceptor myInterceptor; + private DaoConfig myDaoConfig; @After public void afterResetDao() { - SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); + SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); BaseHapiFhirDao.setValidationDisabledForUnitTest(false); } @Before public void before() { - SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); + SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); } @Test @@ -83,8 +83,6 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes }); myEntityManager.clear(); - - myInterceptor.start(); } /** @@ -104,9 +102,6 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes assertNotNull(id.getIdPart()); BaseHapiFhirDao.setValidationDisabledForUnitTest(false); - - myInterceptor.start(); - } /** @@ -124,9 +119,6 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes IIdType id = mySubscriptionDao.create(s).getId().toUnqualifiedVersionless(); BaseHapiFhirDao.setValidationDisabledForUnitTest(false); - - myInterceptor.start(); - } /** @@ -145,9 +137,6 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes assertNotNull(id.getIdPart()); BaseHapiFhirDao.setValidationDisabledForUnitTest(false); - - myInterceptor.start(); - } @AfterClass 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 b07b4b3f281..e88997bfb2a 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 @@ -15,7 +15,7 @@ import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; @@ -223,8 +223,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { @Qualifier("mySearchParameterDaoR4") protected IFhirResourceDao mySearchParameterDao; @Autowired - protected ISearchParamPresenceSvc mySearchParamPresenceSvc; - @Autowired protected ISearchParamRegistry mySearchParamRegsitry; @Autowired protected IStaleSearchDeletingSvc myStaleSearchDeletingSvc; @@ -273,6 +271,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest { protected ICacheWarmingSvc myCacheWarmingSvc; @Autowired private JpaValidationSupportChainR4 myJpaValidationSupportChainR4; + @Autowired + protected SubscriptionRegistry mySubscriptionRegistry; @After() public void afterCleanupDao() { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java index 9065285af1d..e8a1a7bbe8a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java @@ -2,8 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingSubscriber; -import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; +import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; @@ -12,7 +11,6 @@ import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; @@ -23,19 +21,16 @@ import static org.junit.Assert.*; public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test { - @Autowired - private SubscriptionRestHookInterceptor myInterceptor; - @After public void afterResetDao() { - SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); + SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); BaseHapiFhirDao.setValidationDisabledForUnitTest(false); } @Before public void before() { - SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); + SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); } @Test @@ -83,8 +78,6 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test { }); myEntityManager.clear(); - - myInterceptor.start(); } /** @@ -104,9 +97,6 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test { assertNotNull(id.getIdPart()); BaseHapiFhirDao.setValidationDisabledForUnitTest(false); - - myInterceptor.start(); - } /** @@ -124,9 +114,6 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test { IIdType id = mySubscriptionDao.create(s).getId().toUnqualifiedVersionless(); BaseHapiFhirDao.setValidationDisabledForUnitTest(false); - - myInterceptor.start(); - } /** @@ -145,9 +132,6 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test { assertNotNull(id.getIdPart()); BaseHapiFhirDao.setValidationDisabledForUnitTest(false); - - myInterceptor.start(); - } @AfterClass diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java index 66a932188fe..11a6830f2fa 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; @@ -89,6 +90,11 @@ public class SearchParamExtractorR4Test { public Collection getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) { return null; } + + @Override + public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) { + // nothing + } }; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java index 0fff1fceb19..3e4e064616e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java @@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.provider; import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; @@ -46,7 +45,6 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test { protected static Server ourServer; protected static String ourServerBase; protected static GenericWebApplicationContext ourWebApplicationContext; - protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor; protected static DatabaseBackedPagingProvider ourPagingProvider; protected static PlatformTransactionManager ourTxManager; protected static Integer ourConnectionPoolSize; @@ -101,7 +99,6 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test { ourWebApplicationContext.setParent(myAppCtx); ourWebApplicationContext.refresh(); - ourRestHookSubscriptionInterceptor = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class); ourTxManager = ourWebApplicationContext.getBean(PlatformTransactionManager.class); proxyHandler.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ourWebApplicationContext); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java index 62e2534f45d..f897c237199 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/BaseResourceProviderDstu3Test.java @@ -7,8 +7,6 @@ import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu3; -import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor; -import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.parser.StrictErrorHandler; @@ -60,8 +58,6 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { protected static GenericWebApplicationContext ourWebApplicationContext; protected static SearchParamRegistryDstu3 ourSearchParamRegistry; protected static DatabaseBackedPagingProvider ourPagingProvider; - protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor; - protected static SubscriptionEmailInterceptor ourEmailSubscriptionInterceptor; protected static ISearchDao mySearchEntityDao; protected static ISearchCoordinatorSvc mySearchCoordinatorSvc; private static Server ourServer; @@ -158,8 +154,6 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { myValidationSupport = wac.getBean(JpaValidationSupportChainDstu3.class); mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class); mySearchEntityDao = wac.getBean(ISearchDao.class); - ourRestHookSubscriptionInterceptor = wac.getBean(SubscriptionRestHookInterceptor.class); - ourEmailSubscriptionInterceptor = wac.getBean(SubscriptionEmailInterceptor.class); ourSearchParamRegistry = wac.getBean(SearchParamRegistryDstu3.class); ourSubscriptionTriggeringProvider = wac.getBean(SubscriptionTriggeringProvider.class); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java index 8fe97c13e6d..7d5ec840386 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseResourceProviderR4Test.java @@ -3,10 +3,11 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; -import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4; +import ca.uhn.fhir.jpa.subscription.SubscriptionMatcherInterceptor; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; @@ -33,6 +34,7 @@ import org.hl7.fhir.r4.model.Patient; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; @@ -61,7 +63,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { protected static ISearchDao mySearchEntityDao; protected static ISearchCoordinatorSvc mySearchCoordinatorSvc; protected static GenericWebApplicationContext ourWebApplicationContext; - protected static SubscriptionRestHookInterceptor ourReskHookSubscriptionInterceptor; + protected static SubscriptionMatcherInterceptor ourSubscriptionMatcherInterceptor; private static Server ourServer; protected IGenericClient ourClient; protected ResourceCountCache ourResourceCountsCache; @@ -69,6 +71,9 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { private Object ourGraphQLProvider; private boolean ourRestHookSubscriptionInterceptorRequested; + @Autowired + protected SubscriptionLoader mySubscriptionLoader; + public BaseResourceProviderR4Test() { super(); } @@ -156,7 +161,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class); mySearchEntityDao = wac.getBean(ISearchDao.class); ourSearchParamRegistry = wac.getBean(SearchParamRegistryR4.class); - ourReskHookSubscriptionInterceptor = wac.getBean(SubscriptionRestHookInterceptor.class); + ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class); myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000); @@ -177,19 +182,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { } } - /** - * This is lazy created so we only ask for it if its needed - */ - protected SubscriptionRestHookInterceptor getRestHookSubscriptionInterceptor() { - SubscriptionRestHookInterceptor retVal = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class); - ourRestHookSubscriptionInterceptorRequested = true; - return retVal; - } - - protected boolean hasRestHookSubscriptionInterceptor() { - return ourRestHookSubscriptionInterceptorRequested; - } - protected boolean shouldLogClient() { return true; } @@ -206,21 +198,20 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test { return names; } - protected void waitForRegisteredSubscriptionCount(int theSize) throws Exception { + protected void waitForActivatedSubscriptionCount(int theSize) throws Exception { for (int i = 0; ; i++) { if (i == 10) { fail("Failed to init subscriptions"); } try { - getRestHookSubscriptionInterceptor().doInitSubscriptions(); + mySubscriptionLoader.initSubscriptions(); break; } catch (ResourceVersionConflictException e) { Thread.sleep(250); } } - SubscriptionRestHookInterceptor interceptor = getRestHookSubscriptionInterceptor(); - TestUtil.waitForSize(theSize, () -> interceptor.getRegisteredSubscriptions().size()); + TestUtil.waitForSize(theSize, () -> mySubscriptionRegistry.size()); Thread.sleep(500); } 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 13011a0dbfd..4b644ca3458 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 @@ -19,7 +19,6 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.hamcrest.Matchers; -import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.*; @@ -38,7 +37,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; 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; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/BaseSubscriptionsR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java similarity index 88% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/BaseSubscriptionsR4Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java index 04f6a800449..0085e922499 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/BaseSubscriptionsR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java @@ -1,11 +1,9 @@ -package ca.uhn.fhir.jpa.subscription.r4; +package ca.uhn.fhir.jpa.subscription; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; -import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor; -import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test; +import ca.uhn.fhir.jpa.subscription.module.SubscriptionChannel; import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.Update; @@ -26,7 +24,7 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.*; import org.junit.*; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.messaging.support.ExecutorSubscribableChannel; +import org.springframework.messaging.SubscribableChannel; import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; @@ -39,7 +37,6 @@ import java.util.List; public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSubscriptionsR4Test.class); - private static int ourListenerPort; private static RestfulServer ourListenerRestServer; private static Server ourListenerServer; @@ -50,9 +47,9 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test @Autowired private SingleQueryCountHolder myCountHolder; @Autowired - protected DaoConfig myDaoConfig; + protected SubscriptionTestUtil mySubscriptionTestUtil; @Autowired - private DaoRegistry myDaoRegistry; + protected SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor; protected CountingInterceptor myCountingInterceptor; @@ -65,7 +62,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test @After public void afterUnregisterRestHookListener() { - BaseSubscriptionInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(false); + SubscriptionMatcherInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(false); for (IIdType next : mySubscriptionIds) { IIdType nextId = next.toUnqualifiedVersionless(); @@ -81,12 +78,12 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test ourLog.info("Done deleting all subscriptions"); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); - ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor()); + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); } @Before public void beforeRegisterRestHookListener() { - ourRestServer.registerInterceptor(getRestHookSubscriptionInterceptor()); + mySubscriptionTestUtil.registerRestHookInterceptor(); } @Before @@ -101,12 +98,12 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test for (IBaseResource next : BundleUtil.toListOfResources(myFhirCtx, allSubscriptions)) { ourClient.delete().resource(next).execute(); } - waitForRegisteredSubscriptionCount(0); + waitForActivatedSubscriptionCount(0); - ExecutorSubscribableChannel processingChannel = (ExecutorSubscribableChannel) getRestHookSubscriptionInterceptor().getProcessingChannel(); - processingChannel.setInterceptors(new ArrayList<>()); + SubscriptionChannel processingChannel = mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest(); + processingChannel.clearInterceptorsForUnitTest(); myCountingInterceptor = new CountingInterceptor(); - processingChannel.addInterceptor(myCountingInterceptor); + processingChannel.addInterceptorForUnitTest(myCountingInterceptor); } @@ -135,7 +132,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test protected void waitForQueueToDrain() throws InterruptedException { - RestHookTestDstu2Test.waitForQueueToDrain(getRestHookSubscriptionInterceptor()); + mySubscriptionTestUtil.waitForQueueToDrain(); } @PostConstruct @@ -156,8 +153,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test MethodOutcome methodOutcome = ourClient.create().resource(observation).execute(); - String observationId = methodOutcome.getId().getIdPart(); - observation.setId(observationId); + observation.setId(methodOutcome.getId()); return observation; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/CountingInterceptor.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/CountingInterceptor.java similarity index 92% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/CountingInterceptor.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/CountingInterceptor.java index 287c3c8b898..ab63d835282 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/CountingInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/CountingInterceptor.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.r4; +package ca.uhn.fhir.jpa.subscription; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirClientSearchParamProviderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProviderTest.java similarity index 84% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirClientSearchParamProviderTest.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProviderTest.java index f75f67152f5..6af748eda5d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirClientSearchParamProviderTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProviderTest.java @@ -1,10 +1,8 @@ -package ca.uhn.fhir.jpa.subscription.r4; +package ca.uhn.fhir.jpa.subscription; -import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; -import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.subscription.FhirClientSearchParamProvider; +import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSearchParamProvider; import ca.uhn.fhir.rest.api.MethodOutcome; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Enumerations; @@ -20,18 +18,18 @@ import static org.junit.Assert.assertEquals; public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test { @Autowired - BaseSearchParamRegistry mySearchParamRegistry; + ISearchParamRegistry mySearchParamRegistry; @Autowired ISearchParamProvider origSearchParamProvider; @Before public void useFhirClientSearchParamProvider() { - mySearchParamRegistry.setSearchParamProvider(new FhirClientSearchParamProvider(ourClient)); + mySearchParamRegistry.setSearchParamProviderForUnitTest(new FhirClientSearchParamProvider(ourClient)); } @After public void revert() { - mySearchParamRegistry.setSearchParamProvider(origSearchParamProvider); + mySearchParamRegistry.setSearchParamProviderForUnitTest(origSearchParamProvider); } @Test @@ -48,7 +46,7 @@ public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test { mySearchParameterDao.create(sp); mySearchParamRegsitry.forceRefresh(); createSubscription(criteria, "application/json"); - waitForRegisteredSubscriptionCount(1); + waitForActivatedSubscriptionCount(1); { Observation observation = new Observation(); @@ -81,8 +79,5 @@ public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test { waitForQueueToDrain(); waitForSize(2, ourUpdatedObservations); } - } - - } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSubscriptionProviderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSubscriptionProviderTest.java new file mode 100644 index 00000000000..5d23b900dc1 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSubscriptionProviderTest.java @@ -0,0 +1,61 @@ +package ca.uhn.fhir.jpa.subscription; + +import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; +import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSubscriptionProvider; +import ca.uhn.fhir.rest.api.Constants; +import org.hl7.fhir.dstu3.model.Subscription; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class FhirClientSubscriptionProviderTest extends BaseSubscriptionsR4Test { + @Autowired + SubscriptionLoader mySubscriptionLoader; + @Autowired + ISubscriptionProvider origSubscriptionProvider; + @Autowired + AutowireCapableBeanFactory autowireCapableBeanFactory; + + @Before + public void useFhirClientSubscriptionProvider() { + FhirClientSubscriptionProvider subscriptionProvider = new FhirClientSubscriptionProvider(ourClient); + // This bean is only available in the standalone subscription context, so we have to autowire it manually. + autowireCapableBeanFactory.autowireBean(subscriptionProvider); + mySubscriptionLoader.setSubscriptionProviderForUnitTest(subscriptionProvider); + } + + @After + public void revert() { + mySubscriptionLoader.setSubscriptionProviderForUnitTest(origSubscriptionProvider); + } + + private String myCode = "1000000050"; + + @Test + public void testSubscriptionLoaderFhirClient() throws Exception { + String payload = "application/fhir+json"; + + String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml"; + String criteria2 = "Observation?code=SNOMED-CT|" + myCode + "111&_format=xml"; + + + List subs = new ArrayList<>(); + createSubscription(criteria1, payload); + createSubscription(criteria2, payload); + waitForActivatedSubscriptionCount(2); + + sendObservation(myCode, "SNOMED-CT"); + + waitForSize(0, ourCreatedObservations); + waitForSize(1, ourUpdatedObservations); + assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0)); + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirR4Util.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirR4Util.java similarity index 93% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirR4Util.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirR4Util.java index a2b085d7174..9bad30dd6ae 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirR4Util.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirR4Util.java @@ -1,10 +1,11 @@ -package ca.uhn.fhir.jpa.subscription.r4; - -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.instance.model.api.*; +package ca.uhn.fhir.jpa.subscription; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.api.IGenericClient; +import org.hl7.fhir.instance.model.api.IBaseCoding; +import org.hl7.fhir.instance.model.api.IBaseMetaType; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.*; public class FhirR4Util { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTestUtil.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTestUtil.java new file mode 100644 index 00000000000..f2347c48e87 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTestUtil.java @@ -0,0 +1,86 @@ +package ca.uhn.fhir.jpa.subscription; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.subscription.module.LinkedBlockingQueueSubscriptionChannel; +import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender; +import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender; +import ca.uhn.fhir.jpa.subscription.module.subscriber.email.SubscriptionDeliveringEmailSubscriber; +import org.hl7.fhir.instance.model.Subscription; +import org.hl7.fhir.instance.model.api.IIdType; +import org.springframework.beans.factory.annotation.Autowired; + +public class SubscriptionTestUtil { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionTestUtil.class); + + private JavaMailEmailSender myEmailSender; + + @Autowired + private DaoConfig myDaoConfig; + @Autowired + private SubscriptionInterceptorLoader mySubscriptionInterceptorLoader; + @Autowired + private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor; + @Autowired + private SubscriptionRegistry mySubscriptionRegistry; + + public int getExecutorQueueSize() { + LinkedBlockingQueueSubscriptionChannel channel = (LinkedBlockingQueueSubscriptionChannel) mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest(); + return channel.getQueueSizeForUnitTest(); + } + + // TODO KHS replace this and similar functions with CountdownLatch + public void waitForQueueToDrain() throws InterruptedException { + Thread.sleep(100); + ourLog.info("Executor work queue has {} items", getExecutorQueueSize()); + if (getExecutorQueueSize() > 0) { + while (getExecutorQueueSize() > 0) { + Thread.sleep(50); + } + ourLog.info("Executor work queue has {} items", getExecutorQueueSize()); + } + Thread.sleep(100); + } + + public void registerEmailInterceptor() { + myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL); + mySubscriptionInterceptorLoader.registerInterceptors(); + } + + public void registerRestHookInterceptor() { + myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK); + mySubscriptionInterceptorLoader.registerInterceptors(); + } + + public void registerWebSocketInterceptor() { + myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.WEBSOCKET); + mySubscriptionInterceptorLoader.registerInterceptors(); + } + + public void unregisterSubscriptionInterceptor() { + myDaoConfig.clearSupportedSubscriptionTypesForUnitTest(); + mySubscriptionInterceptorLoader.unregisterInterceptorsForUnitTest(); + } + + public int getExecutorQueueSizeForUnitTests() { + return getExecutorQueueSize(); + } + + public void initEmailSender(int theListenerPort) { + myEmailSender = new JavaMailEmailSender(); + myEmailSender.setSmtpServerHostname("localhost"); + myEmailSender.setSmtpServerPort(theListenerPort); + myEmailSender.start(); + } + + public void setEmailSender(IIdType theIdElement) { + ActiveSubscription activeSubscription = mySubscriptionRegistry.get(theIdElement.getIdPart()); + SubscriptionDeliveringEmailSubscriber subscriber = (SubscriptionDeliveringEmailSubscriber) activeSubscription.getDeliveryHandlerForUnitTest(); + subscriber.setEmailSender(myEmailSender); + } + + public IEmailSender getEmailSender() { + return myEmailSender; + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/EmailSubscriptionDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu2Test.java similarity index 73% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/EmailSubscriptionDstu2Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu2Test.java index a722b4ac937..582ad7aaa57 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/EmailSubscriptionDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu2Test.java @@ -1,10 +1,7 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.email; -import ca.uhn.fhir.jpa.config.BaseConfig; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test; -import ca.uhn.fhir.jpa.subscription.email.JavaMailEmailSender; -import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; @@ -22,8 +19,6 @@ import org.junit.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.core.task.AsyncTaskExecutor; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; @@ -42,12 +37,7 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test { private List mySubscriptionIds = new ArrayList<>(); @Autowired - private SubscriptionEmailInterceptor mySubscriber; - @Autowired - private List> myResourceDaos; - @Autowired - @Qualifier(BaseConfig.TASK_EXECUTOR_NAME) - private AsyncTaskExecutor myAsyncTaskExecutor; + private SubscriptionTestUtil mySubscriptionTestUtil; @After public void after() throws Exception { @@ -58,38 +48,16 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test { ourClient.delete().resourceById(next).execute(); } mySubscriptionIds.clear(); - - ourRestServer.unregisterInterceptor(mySubscriber); + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); } @Before public void before() throws Exception { super.before(); - JavaMailEmailSender emailSender = new JavaMailEmailSender(); - emailSender.setSmtpServerHostname("localhost"); - emailSender.setSmtpServerPort(ourListenerPort); - emailSender.start(); + mySubscriptionTestUtil.initEmailSender(ourListenerPort); - mySubscriber.setEmailSender(emailSender); - mySubscriber.setResourceDaos(myResourceDaos); - mySubscriber.setFhirContext(myFhirCtx); - mySubscriber.setTxManager(ourTxManager); - mySubscriber.setAsyncTaskExecutorForUnitTest(myAsyncTaskExecutor); - mySubscriber.start(); - ourRestServer.registerInterceptor(mySubscriber); - -// ourLog.info("Sending test email to warm up the server"); -// EmailDetails details = new EmailDetails(); -// details.setFrom("a@a.com"); -// details.setTo(Arrays.asList("b@b.com")); -// details.setSubjectTemplate("SUBJ"); -// details.setBodyTemplate("BODY"); -// emailSender.send(details); -// ourLog.info("Done sending test email to warm up the server"); -// Store store = ourTestSmtp.getManagers().getImapHostManager().getStore(); -// MailFolder mailbox = store.getMailbox(ImapConstants.USER_NAMESPACE); -// mailbox.deleteAllMessages(); + mySubscriptionTestUtil.registerEmailInterceptor(); } private Subscription createSubscription(String criteria, String payload, String endpoint) throws InterruptedException { @@ -108,7 +76,7 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test { subscription.setId(methodOutcome.getId().getIdPart()); mySubscriptionIds.add(methodOutcome.getId()); - RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.waitForQueueToDrain(); return subscription; } @@ -138,8 +106,8 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test { String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; Subscription subscription1 = createSubscription(criteria1, payload, "to1@example.com,to2@example.com"); - RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); - + mySubscriptionTestUtil.waitForQueueToDrain(); + mySubscriptionTestUtil.setEmailSender(subscription1.getIdElement()); assertEquals(0, Arrays.asList(ourTestSmtp.getReceivedMessages()).size()); Observation observation1 = sendObservation(code, "SNOMED-CT"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java index 4035b3a9ed3..02705f1d770 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java @@ -2,9 +2,9 @@ package ca.uhn.fhir.jpa.subscription.email; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; -import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; -import ca.uhn.fhir.jpa.util.JpaConstants; import ca.uhn.fhir.rest.api.MethodOutcome; import com.google.common.collect.Lists; import com.icegreen.greenmail.store.FolderException; @@ -13,6 +13,7 @@ import com.icegreen.greenmail.util.ServerSetup; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.*; +import org.springframework.beans.factory.annotation.Autowired; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; @@ -21,7 +22,7 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** * Test the rest-hook subscriptions @@ -29,6 +30,10 @@ import static org.junit.Assert.*; public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(EmailSubscriptionDstu3Test.class); + + @Autowired + private SubscriptionTestUtil mySubscriptionTestUtil; + private static List ourCreatedObservations = Lists.newArrayList(); private static int ourListenerPort; private static List ourContentTypes = new ArrayList<>(); @@ -51,23 +56,17 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { ourLog.info("Done deleting all subscriptions"); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); - ourRestServer.unregisterInterceptor(ourEmailSubscriptionInterceptor); - + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); } @Before public void beforeRegisterEmailListener() throws FolderException { ourTestSmtp.purgeEmailFromAllMailboxes(); - ; - ourRestServer.registerInterceptor(ourEmailSubscriptionInterceptor); + mySubscriptionTestUtil.registerEmailInterceptor(); - JavaMailEmailSender emailSender = new JavaMailEmailSender(); - emailSender.setSmtpServerHostname("localhost"); - emailSender.setSmtpServerPort(ourListenerPort); - emailSender.start(); + mySubscriptionTestUtil.initEmailSender(ourListenerPort); - ourEmailSubscriptionInterceptor.setEmailSender(emailSender); - ourEmailSubscriptionInterceptor.setDefaultFromAddress("123@hapifhir.io"); + myDaoConfig.setEmailFromAddress("123@hapifhir.io"); } private Subscription createSubscription(String theCriteria, String thePayload) throws InterruptedException { @@ -115,8 +114,9 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { String code = "1000000050"; String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; - createSubscription(criteria1, payload); + Subscription subscription = createSubscription(criteria1, payload); waitForQueueToDrain(); + mySubscriptionTestUtil.setEmailSender(subscription.getIdElement()); sendObservation(code, "SNOMED-CT"); waitForQueueToDrain(); @@ -150,19 +150,18 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { Assert.assertNotNull(subscriptionTemp); subscriptionTemp.getChannel().addExtension() - .setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM) + .setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM) .setValue(new StringType("mailto:myfrom@from.com")); subscriptionTemp.getChannel().addExtension() - .setUrl(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE) + .setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE) .setValue(new StringType("This is a subject")); subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless()); - ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(subscriptionTemp)); ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute(); waitForQueueToDrain(); - + mySubscriptionTestUtil.setEmailSender(subscriptionTemp.getIdElement()); sendObservation(code, "SNOMED-CT"); waitForQueueToDrain(); @@ -197,10 +196,10 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { Subscription subscriptionTemp = ourClient.read(Subscription.class, sub1.getId()); Assert.assertNotNull(subscriptionTemp); subscriptionTemp.getChannel().addExtension() - .setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM) + .setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM) .setValue(new StringType("myfrom@from.com")); subscriptionTemp.getChannel().addExtension() - .setUrl(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE) + .setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE) .setValue(new StringType("This is a subject")); subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless()); @@ -208,6 +207,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { ourLog.info("Subscription ID is: {}", id.getValue()); waitForQueueToDrain(); + mySubscriptionTestUtil.setEmailSender(subscriptionTemp.getIdElement()); sendObservation(code, "SNOMED-CT"); waitForQueueToDrain(); @@ -238,7 +238,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { } private void waitForQueueToDrain() throws InterruptedException { - RestHookTestDstu2Test.waitForQueueToDrain(ourEmailSubscriptionInterceptor); + mySubscriptionTestUtil.waitForQueueToDrain(); } @AfterClass diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSenderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSenderTest.java index 828441acb29..6c6b8a6cd59 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSenderTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSenderTest.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jpa.subscription.email; +import ca.uhn.fhir.jpa.subscription.module.subscriber.email.EmailDetails; +import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import com.icegreen.greenmail.util.GreenMail; import com.icegreen.greenmail.util.GreenMailUtil; @@ -15,7 +17,7 @@ import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import java.util.Arrays; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; public class JavaMailEmailSenderTest { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemoryTestR4.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherTestR4.java similarity index 98% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemoryTestR4.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherTestR4.java index 943987dfc5b..c4ece8f0207 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemoryTestR4.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherTestR4.java @@ -1,10 +1,10 @@ -package ca.uhn.fhir.jpa.subscription.matcher; +package ca.uhn.fhir.jpa.subscription.module.matcher; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.*; @@ -29,18 +29,18 @@ import static org.junit.Assert.*; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {TestR4Config.class}) -public class SubscriptionMatcherInMemoryTestR4 { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionMatcherInMemoryTestR4.class); +public class InMemorySubscriptionMatcherTestR4 { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(InMemorySubscriptionMatcherTestR4.class); @Autowired - SubscriptionMatcherInMemory mySubscriptionMatcherInMemory; + InMemorySubscriptionMatcher myInMemorySubscriptionMatcher; @Autowired FhirContext myContext; private SubscriptionMatchResult match(IBaseResource resource, SearchParameterMap params) { String criteria = params.toNormalizedQueryString(myContext); ourLog.info("Criteria: <{}>", criteria); - return mySubscriptionMatcherInMemory.match(criteria, resource); + return myInMemorySubscriptionMatcher.match(criteria, resource); } private void assertUnsupported(IBaseResource resource, SearchParameterMap params) { @@ -380,18 +380,16 @@ public class SubscriptionMatcherInMemoryTestR4 { params.add(Patient.SP_FAMILY, new StringParam("testSearchNameParam01Fam")); try { String criteria = params.toNormalizedQueryString(myContext); - ResourceModifiedMessage msg = new ResourceModifiedMessage(); + ResourceModifiedMessage msg = new ResourceModifiedMessage(myContext, patient, ResourceModifiedMessage.OperationTypeEnum.CREATE); msg.setSubscriptionId("Subscription/123"); msg.setId(new IdType("Patient/ABC")); - msg.setNewPayload(myContext, patient); - SubscriptionMatchResult result = mySubscriptionMatcherInMemory.match(criteria, msg); + SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, msg); fail(); } catch (InternalErrorException e){ assertEquals("Failure processing resource ID[Patient/ABC] for subscription ID[Subscription/123]: Invalid resource reference found at path[Patient.managingOrganization] - Does not contain resource type - urn:uuid:13720262-b392-465f-913e-54fb198ff954", e.getMessage()); } } - @Test public void testSearchResourceReferenceOnlyCorrectPath() { Organization org = new Organization(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookActivatesPreExistingSubscriptionsR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookActivatesPreExistingSubscriptionsR4Test.java similarity index 87% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookActivatesPreExistingSubscriptionsR4Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookActivatesPreExistingSubscriptionsR4Test.java index 8d0c3a44baa..8d34fd1f8c2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookActivatesPreExistingSubscriptionsR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookActivatesPreExistingSubscriptionsR4Test.java @@ -1,9 +1,9 @@ -package ca.uhn.fhir.jpa.subscription.r4; +package ca.uhn.fhir.jpa.subscription.resthook; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; -import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test; -import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingSubscriber; +import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.api.Constants; @@ -20,6 +20,7 @@ import org.hl7.fhir.r4.model.*; import org.junit.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; @@ -28,7 +29,6 @@ import java.util.Enumeration; import java.util.List; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourceProviderR4Test { @@ -41,20 +41,23 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc private static List ourContentTypes = Collections.synchronizedList(new ArrayList<>()); private static List ourHeaders = Collections.synchronizedList(new ArrayList<>()); + @Autowired + private SubscriptionTestUtil mySubscriptionTestUtil; + @After public void afterResetSubscriptionActivatingInterceptor() { - SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); + SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); } @After public void afterUnregisterRestHookListener() { - ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor()); + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); } @Before public void beforeSetSubscriptionActivatingInterceptor() { - SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); - getRestHookSubscriptionInterceptor().initSubscriptions(); + SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); + mySubscriptionLoader.initSubscriptions(); } @@ -105,11 +108,8 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc createSubscription(criteria1, payload, ourListenerServerBase); createSubscription(criteria2, payload, ourListenerServerBase); - ourRestServer.registerInterceptor(getRestHookSubscriptionInterceptor()); - getRestHookSubscriptionInterceptor().initSubscriptions(); - - assertTrue(hasRestHookSubscriptionInterceptor()); - + mySubscriptionTestUtil.registerRestHookInterceptor(); + mySubscriptionLoader.initSubscriptions(); sendObservation(code, "SNOMED-CT"); @@ -120,9 +120,7 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc } private void waitForQueueToDrain() throws InterruptedException { - if (hasRestHookSubscriptionInterceptor()) { - RestHookTestDstu2Test.waitForQueueToDrain(getRestHookSubscriptionInterceptor()); - } + mySubscriptionTestUtil.waitForQueueToDrain(); } public static class ObservationListener implements IResourceProvider { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu2Test.java similarity index 93% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu2Test.java index bfe49f57518..2a79125a84e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu2Test.java @@ -1,8 +1,9 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.resthook; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.resource.Observation; @@ -27,11 +28,13 @@ import org.eclipse.jetty.servlet.ServletHolder; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.*; +import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; /** * Test the rest-hook subscriptions @@ -47,6 +50,9 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test { private static List ourUpdatedObservations = Lists.newArrayList(); private List mySubscriptionIds = new ArrayList(); + @Autowired + private SubscriptionTestUtil mySubscriptionTestUtil; + @After public void afterUnregisterRestHookListener() { ourLog.info("** AFTER **"); @@ -62,12 +68,12 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test { ourLog.info("Done deleting all subscriptions"); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); - ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); } @Before public void beforeRegisterRestHookListener() { - ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.registerRestHookInterceptor(); } @Before @@ -279,7 +285,7 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test { } private void waitForQueueToDrain() throws InterruptedException { - RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.waitForQueueToDrain(); } @BeforeClass @@ -309,18 +315,6 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test { ourListenerServer.stop(); } - public static void waitForQueueToDrain(BaseSubscriptionInterceptor theSubscriptionInterceptor) throws InterruptedException { - Thread.sleep(100); - ourLog.info("Executor work queue has {} items", theSubscriptionInterceptor.getExecutorQueueSizeForUnitTests()); - if (theSubscriptionInterceptor.getExecutorQueueSizeForUnitTests() > 0) { - while (theSubscriptionInterceptor.getExecutorQueueSizeForUnitTests() > 0) { - Thread.sleep(50); - } - ourLog.info("Executor work queue has {} items", theSubscriptionInterceptor.getExecutorQueueSizeForUnitTests()); - } - Thread.sleep(100); - } - public static class ObservationListener implements IResourceProvider { @Create diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java similarity index 97% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu3Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java index fcaa6964165..97716aac54f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java @@ -1,9 +1,10 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.resthook; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.Update; @@ -21,6 +22,7 @@ import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.*; +import org.springframework.beans.factory.annotation.Autowired; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; @@ -45,6 +47,9 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test { private static List ourContentTypes = Collections.synchronizedList(new ArrayList<>()); private List mySubscriptionIds = Collections.synchronizedList(new ArrayList<>()); + @Autowired + private SubscriptionTestUtil mySubscriptionTestUtil; + @After public void afterUnregisterRestHookListener() { ourLog.info("**** Starting @After *****"); @@ -61,13 +66,12 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test { ourLog.info("Done deleting all subscriptions"); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); - ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor); - + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); } @Before public void beforeRegisterRestHookListener() { - ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.registerRestHookInterceptor(); } @Before @@ -346,7 +350,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test { } private void waitForQueueToDrain() throws InterruptedException { - RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.waitForQueueToDrain(); } @BeforeClass diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java similarity index 78% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java index 81ec08ec3c7..b6ae76bc815 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java @@ -1,42 +1,26 @@ -package ca.uhn.fhir.jpa.subscription.r4; +package ca.uhn.fhir.jpa.subscription.resthook; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor; -import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test; -import ca.uhn.fhir.jpa.util.JpaConstants; -import ca.uhn.fhir.rest.annotation.Create; -import ca.uhn.fhir.rest.annotation.ResourceParam; -import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.jpa.config.StoppableSubscriptionDeliveringRestHookSubscriber; +import ca.uhn.fhir.jpa.subscription.BaseSubscriptionsR4Test; +import ca.uhn.fhir.jpa.subscription.SubscriptionMatcherInterceptor; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants; import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import ca.uhn.fhir.util.BundleUtil; -import ca.uhn.fhir.util.PortUtil; -import net.ttddyy.dsproxy.QueryCount; -import net.ttddyy.dsproxy.listener.SingleQueryCountHolder; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; import org.hl7.fhir.instance.model.api.IBaseBundle; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.*; -import org.junit.*; +import org.junit.After; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.messaging.support.ExecutorSubscribableChannel; -import javax.annotation.PostConstruct; -import javax.servlet.http.HttpServletRequest; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItem; @@ -46,7 +30,16 @@ import static org.junit.Assert.*; * Test the rest-hook subscriptions */ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestR4Test.class); + private static final Logger ourLog = LoggerFactory.getLogger(RestHookTestR4Test.class); + + @Autowired + StoppableSubscriptionDeliveringRestHookSubscriber myStoppableSubscriptionDeliveringRestHookSubscriber; + + @After + public void cleanupStoppableSubscriptionDeliveringRestHookSubscriber() { + myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(null); + myStoppableSubscriptionDeliveringRestHookSubscriber.unPause(); + } @Test public void testRestHookSubscriptionApplicationFhirJson() throws Exception { @@ -58,7 +51,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { createSubscription(criteria1, payload); createSubscription(criteria2, payload); - waitForRegisteredSubscriptionCount(2); + waitForActivatedSubscriptionCount(2); sendObservation(code, "SNOMED-CT"); @@ -75,10 +68,10 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { String payload = "application/fhir+json"; createSubscription(criteria, payload); - waitForRegisteredSubscriptionCount(1); + waitForActivatedSubscriptionCount(1); for (int i = 0; i < 5; i++) { - Integer changes = ourReskHookSubscriptionInterceptor.doInitSubscriptions(); - assertEquals(0, changes.intValue()); + int changes = this.mySubscriptionLoader.doInitSubscriptionsForUnitTest(); + assertEquals(0, changes); } } @@ -92,7 +85,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { createSubscription(criteria1, payload); createSubscription(criteria2, payload); - waitForRegisteredSubscriptionCount(2); + waitForActivatedSubscriptionCount(2); Observation obs = sendObservation(code, "SNOMED-CT"); @@ -122,32 +115,137 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { String code = "1000000050"; String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; - waitForRegisteredSubscriptionCount(0); + waitForActivatedSubscriptionCount(0); Subscription subscription1 = createSubscription(criteria1, payload); - waitForRegisteredSubscriptionCount(1); + waitForActivatedSubscriptionCount(1); - int modCount = myCountingInterceptor.getSentCount(); - subscription1 - .getChannel() - .addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS, new BooleanType("true")); - subscription1 - .getChannel() - .addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION, new BooleanType("true")); - ourLog.info("** About to update subscription"); - ourClient.update().resource(subscription1).execute(); - waitForSize(modCount + 1, () -> myCountingInterceptor.getSentCount()); ourLog.info("** About to send observation"); Observation observation1 = sendObservation(code, "SNOMED-CT"); - // Should see 1 subscription notification waitForQueueToDrain(); waitForSize(0, ourCreatedObservations); waitForSize(1, ourUpdatedObservations); assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0)); - assertEquals(observation1.getIdElement().getIdPart(), ourUpdatedObservations.get(0).getIdElement().getIdPart()); - assertEquals(null, ourUpdatedObservations.get(0).getIdElement().getVersionIdPart()); + IdType idElement = ourUpdatedObservations.get(0).getIdElement(); + assertEquals(observation1.getIdElement().getIdPart(), idElement.getIdPart()); + // VersionId is present + assertEquals(observation1.getIdElement().getVersionIdPart(), idElement.getVersionIdPart()); + + subscription1 + .getChannel() + .addExtension(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS, new BooleanType("true")); + ourLog.info("** About to update subscription"); + + int modCount = myCountingInterceptor.getSentCount(); + ourClient.update().resource(subscription1).execute(); + waitForSize(modCount + 1, () -> myCountingInterceptor.getSentCount()); + + ourLog.info("** About to send observation"); + Observation observation2 = sendObservation(code, "SNOMED-CT"); + + waitForQueueToDrain(); + waitForSize(0, ourCreatedObservations); + waitForSize(2, ourUpdatedObservations); + assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(1)); + + idElement = ourUpdatedObservations.get(1).getIdElement(); + assertEquals(observation2.getIdElement().getIdPart(), idElement.getIdPart()); + // Now VersionId is stripped + assertEquals(null, idElement.getVersionIdPart()); + } + + @Test + public void testRestHookSubscriptionDoesntGetLatestVersionByDefault() throws Exception { + String payload = "application/json"; + + String code = "1000000050"; + String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; + + waitForActivatedSubscriptionCount(0); + createSubscription(criteria1, payload); + waitForActivatedSubscriptionCount(1); + + myStoppableSubscriptionDeliveringRestHookSubscriber.pause(); + final CountDownLatch countDownLatch = new CountDownLatch(1); + myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(countDownLatch); + + ourLog.info("** About to send observation"); + Observation observation = sendObservation(code, "SNOMED-CT"); + assertEquals("1", observation.getIdElement().getVersionIdPart()); + assertNull(observation.getComment()); + + observation.setComment("changed"); + MethodOutcome methodOutcome = ourClient.update().resource(observation).execute(); + assertEquals("2", methodOutcome.getId().getVersionIdPart()); + assertEquals("changed", observation.getComment()); + + // Wait for our two delivery channel threads to be paused + assertTrue(countDownLatch.await(5L, TimeUnit.SECONDS)); + // Open the floodgates! + myStoppableSubscriptionDeliveringRestHookSubscriber.unPause(); + + + waitForSize(0, ourCreatedObservations); + waitForSize(2, ourUpdatedObservations); + + Observation observation1 = ourUpdatedObservations.get(0); + Observation observation2 = ourUpdatedObservations.get(1); + + assertEquals("1", observation1.getIdElement().getVersionIdPart()); + assertNull(observation1.getComment()); + assertEquals("2", observation2.getIdElement().getVersionIdPart()); + assertEquals("changed", observation2.getComment()); + } + + @Test + public void testRestHookSubscriptionGetsLatestVersionWithFlag() throws Exception { + String payload = "application/json"; + + String code = "1000000050"; + String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; + + waitForActivatedSubscriptionCount(0); + + Subscription subscription = newSubscription(criteria1, payload); + subscription + .getChannel() + .addExtension(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION, new BooleanType("true")); + ourClient.create().resource(subscription).execute(); + + waitForActivatedSubscriptionCount(1); + + myStoppableSubscriptionDeliveringRestHookSubscriber.pause(); + final CountDownLatch countDownLatch = new CountDownLatch(1); + myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(countDownLatch); + + ourLog.info("** About to send observation"); + Observation observation = sendObservation(code, "SNOMED-CT"); + assertEquals("1", observation.getIdElement().getVersionIdPart()); + assertNull(observation.getComment()); + + observation.setComment("changed"); + MethodOutcome methodOutcome = ourClient.update().resource(observation).execute(); + assertEquals("2", methodOutcome.getId().getVersionIdPart()); + assertEquals("changed", observation.getComment()); + + // Wait for our two delivery channel threads to be paused + assertTrue(countDownLatch.await(5L, TimeUnit.SECONDS)); + // Open the floodgates! + myStoppableSubscriptionDeliveringRestHookSubscriber.unPause(); + + + waitForSize(0, ourCreatedObservations); + waitForSize(2, ourUpdatedObservations); + + Observation observation1 = ourUpdatedObservations.get(0); + Observation observation2 = ourUpdatedObservations.get(1); + + assertEquals("2", observation1.getIdElement().getVersionIdPart()); + assertEquals("changed", observation1.getComment()); + assertEquals("2", observation2.getIdElement().getVersionIdPart()); + assertEquals("changed", observation2.getComment()); } @Test @@ -160,7 +258,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { Subscription subscription1 = createSubscription(criteria1, payload); Subscription subscription2 = createSubscription(criteria2, payload); - waitForRegisteredSubscriptionCount(2); + waitForActivatedSubscriptionCount(2); Observation observation1 = sendObservation(code, "SNOMED-CT"); @@ -240,7 +338,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { Subscription subscription1 = createSubscription(criteria1, payload); Subscription subscription2 = createSubscription(criteria2, payload); - waitForRegisteredSubscriptionCount(2); + waitForActivatedSubscriptionCount(2); Observation observation1 = sendObservation(code, "SNOMED-CT"); @@ -318,7 +416,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { Subscription subscription1 = createSubscription(criteria1, payload); Subscription subscription2 = createSubscription(criteria2, payload); - waitForRegisteredSubscriptionCount(2); + waitForActivatedSubscriptionCount(2); ourLog.info("** About to send obervation"); Observation observation1 = sendObservation(code, "SNOMED-CT"); @@ -384,7 +482,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { @Test public void testSubscriptionTriggerViaSubscription() throws Exception { - BaseSubscriptionInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(true); + SubscriptionMatcherInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(true); String payload = "application/xml"; @@ -392,7 +490,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; createSubscription(criteria1, payload); - waitForRegisteredSubscriptionCount(1); + waitForActivatedSubscriptionCount(1); ourLog.info("** About to send obervation"); @@ -490,7 +588,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { Subscription subscription1 = createSubscription(criteria1, payload); Subscription subscription2 = createSubscription(criteria2, payload); - waitForRegisteredSubscriptionCount(2); + waitForActivatedSubscriptionCount(2); Observation observation1 = sendObservation(code, "SNOMED-CT"); @@ -526,7 +624,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { // Add some headers, and we'll also turn back to requested status for fun Subscription subscription = createSubscription(criteria1, payload); - waitForRegisteredSubscriptionCount(1); + waitForActivatedSubscriptionCount(1); subscription.getChannel().addHeader("X-Foo: FOO"); subscription.getChannel().addHeader("X-Bar: BAR"); @@ -553,7 +651,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; Subscription subscription = createSubscription(criteria1, payload); - waitForRegisteredSubscriptionCount(1); + waitForActivatedSubscriptionCount(1); sendObservation(code, "SNOMED-CT"); @@ -644,7 +742,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test { mySearchParameterDao.create(sp); mySearchParamRegsitry.forceRefresh(); createSubscription(criteria, "application/json"); - waitForRegisteredSubscriptionCount(1); + waitForActivatedSubscriptionCount(1); { Observation bodySite = new Observation(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java index c542bb0f9ab..599ee55a986 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java @@ -1,8 +1,9 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.resthook; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.resource.Observation; @@ -25,6 +26,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.hl7.fhir.instance.model.api.IBaseResource; import org.junit.*; +import org.springframework.beans.factory.annotation.Autowired; import java.util.Collections; import java.util.List; @@ -42,6 +44,9 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B private static String ourListenerServerBase; private static List ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList()); + @Autowired + private SubscriptionTestUtil mySubscriptionTestUtil; + @After public void afterUnregisterRestHookListener() { ourLog.info("** AFTER **"); @@ -51,12 +56,12 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B ourLog.info("Done deleting all subscriptions"); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); - myDaoConfig.getInterceptors().remove(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); } @Before public void beforeRegisterRestHookListener() { - myDaoConfig.getInterceptors().add(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.registerRestHookInterceptor(); } @Before @@ -64,11 +69,11 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B ourCreatedObservations.clear(); ourUpdatedObservations.clear(); - ourRestHookSubscriptionInterceptor.initSubscriptions(); + mySubscriptionLoader.initSubscriptions(); } private void waitForQueueToDrain() throws InterruptedException { - RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.waitForQueueToDrain(); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java similarity index 93% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java index 5064960ee91..889a723d4eb 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java @@ -1,29 +1,30 @@ -package ca.uhn.fhir.jpa.subscription; - -import static org.junit.Assert.*; - -import java.util.Collections; -import java.util.List; +package ca.uhn.fhir.jpa.subscription.resthook; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; +import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; +import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import com.google.common.collect.Lists; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.instance.model.api.IBaseResource; import org.junit.*; +import org.springframework.beans.factory.annotation.Autowired; -import com.google.common.collect.Lists; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; -import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; +import java.util.Collections; +import java.util.List; /** * Test the rest-hook subscriptions @@ -38,6 +39,9 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends B private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.class); private static List ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList()); + @Autowired + private SubscriptionTestUtil mySubscriptionTestUtil; + @Override protected boolean shouldLogClient() { return false; @@ -50,21 +54,21 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends B ourClient.delete().resourceConditionalByUrl("Subscription?status=active").execute(); ourLog.info("Done deleting all subscriptions"); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); - - myDaoConfig.getInterceptors().remove(ourRestHookSubscriptionInterceptor); - SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); + + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); + SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); } @Before public void beforeRegisterRestHookListener() { - myDaoConfig.getInterceptors().add(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.registerRestHookInterceptor(); } @Before public void beforeReset() { ourCreatedObservations.clear(); ourUpdatedObservations.clear(); - SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); + SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); } private Subscription createSubscription(String criteria, String payload, String endpoint) throws InterruptedException { @@ -87,7 +91,7 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends B } private void waitForQueueToDrain() throws InterruptedException { - RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.waitForQueueToDrain(); } private Observation sendObservation(String code, String system) throws InterruptedException { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java similarity index 93% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java index d1f95a93a7d..56782fbabef 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java @@ -1,28 +1,30 @@ -package ca.uhn.fhir.jpa.subscription.r4; - -import java.util.Collections; -import java.util.List; - -import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.junit.*; - -import com.google.common.collect.Lists; +package ca.uhn.fhir.jpa.subscription.resthook; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; +import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; +import com.google.common.collect.Lists; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.*; +import org.junit.*; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Collections; +import java.util.List; /** * Test the rest-hook subscriptions @@ -37,6 +39,9 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.class); private static List ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList()); + @Autowired + private SubscriptionTestUtil mySubscriptionTestUtil; + @Override protected boolean shouldLogClient() { return false; @@ -49,13 +54,13 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base ourClient.delete().resourceConditionalByUrl("Subscription?status=active").execute(); ourLog.info("Done deleting all subscriptions"); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); - - myDaoConfig.getInterceptors().remove(getRestHookSubscriptionInterceptor()); + + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); } @Before public void beforeRegisterRestHookListener() { - myDaoConfig.getInterceptors().add(getRestHookSubscriptionInterceptor()); + mySubscriptionTestUtil.registerRestHookInterceptor(); } @Before @@ -84,11 +89,11 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base } private void waitForQueueToDrain() throws InterruptedException { - ourLog.info("QUEUE HAS {} ITEMS", getRestHookSubscriptionInterceptor().getExecutorQueueSizeForUnitTests()); - while (getRestHookSubscriptionInterceptor().getExecutorQueueSizeForUnitTests() > 0) { + ourLog.info("QUEUE HAS {} ITEMS", mySubscriptionTestUtil.getExecutorQueueSizeForUnitTests()); + while (mySubscriptionTestUtil.getExecutorQueueSizeForUnitTests() > 0) { Thread.sleep(250); } - ourLog.info("QUEUE HAS {} ITEMS", getRestHookSubscriptionInterceptor().getExecutorQueueSizeForUnitTests()); + ourLog.info("QUEUE HAS {} ITEMS", mySubscriptionTestUtil.getExecutorQueueSizeForUnitTests()); } private Observation sendObservation(String code, String system) throws InterruptedException { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookWithEventDefinitionR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithEventDefinitionR4Test.java similarity index 90% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookWithEventDefinitionR4Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithEventDefinitionR4Test.java index 8e95fa6506f..dc23c1cf23e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookWithEventDefinitionR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithEventDefinitionR4Test.java @@ -1,7 +1,9 @@ -package ca.uhn.fhir.jpa.subscription.r4; +package ca.uhn.fhir.jpa.subscription.resthook; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; +import ca.uhn.fhir.jpa.subscription.FhirR4Util; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.rest.api.MethodOutcome; import com.google.common.collect.Lists; import org.hl7.fhir.instance.model.api.IIdType; @@ -11,6 +13,7 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.Collections; @@ -44,6 +47,9 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes private String mySubscriptionId; private List mySubscriptionIds = Collections.synchronizedList(new ArrayList<>()); + @Autowired + private SubscriptionTestUtil mySubscriptionTestUtil; + @Override @After public void after() throws Exception { @@ -64,7 +70,7 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes ourLog.info("Done deleting all subscriptions"); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); - ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor()); + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); } @Override @@ -76,6 +82,9 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes } + /** + * Ignored because this feature isn't implemented yet + */ @Test @Ignore public void testSubscriptionAddedTrigger() { @@ -122,7 +131,7 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes @Before public void beforeRegisterRestHookListener() { - ourRestServer.registerInterceptor(getRestHookSubscriptionInterceptor()); + mySubscriptionTestUtil.registerRestHookInterceptor(); } @Before diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java similarity index 97% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringDstu3Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java index e36eace4790..e1efd7e5659 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java @@ -1,9 +1,11 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.resthook; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; +import ca.uhn.fhir.jpa.subscription.SubscriptionTriggeringSvcImpl; import ca.uhn.fhir.jpa.util.JpaConstants; import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.ResourceParam; @@ -50,6 +52,9 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te private static List ourContentTypes = new ArrayList<>(); private List mySubscriptionIds = new ArrayList<>(); + @Autowired + private SubscriptionTestUtil mySubscriptionTestUtil; + @After public void afterUnregisterRestHookListener() { ourLog.info("**** Starting @After *****"); @@ -66,7 +71,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te ourLog.info("Done deleting all subscriptions"); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); - ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); mySubscriptionTriggeringSvc.cancelAll(); mySubscriptionTriggeringSvc.setMaxSubmitPerPass(null); @@ -79,7 +84,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te @Before public void beforeRegisterRestHookListener() { - ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.registerRestHookInterceptor(); } /** @@ -376,7 +381,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te } private void waitForQueueToDrain() throws InterruptedException { - RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); + mySubscriptionTestUtil.waitForQueueToDrain(); } public static class ObservationListener implements IResourceProvider { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaDstu2Test.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu2Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaDstu2Test.java index 492398b6fab..54e1f616fff 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaDstu2Test.java @@ -1,6 +1,8 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.websocket; import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test; +import ca.uhn.fhir.jpa.subscription.FhirDstu2Util; +import ca.uhn.fhir.jpa.subscription.SocketImplementation; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; @@ -27,7 +29,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; // This is currently disabled as the criteria mechanism was a non-standard experiment @Ignore diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaDstu3Test.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu3Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaDstu3Test.java index 9d90b49b931..b213c167654 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaDstu3Test.java @@ -1,6 +1,8 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.websocket; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; +import ca.uhn.fhir.jpa.subscription.FhirDstu3Util; +import ca.uhn.fhir.jpa.subscription.SocketImplementation; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; import org.eclipse.jetty.websocket.api.Session; @@ -18,7 +20,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; // This is currently disabled as the criteria mechanism was a non-standard experiment @Ignore diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithCriteriaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaR4Test.java similarity index 97% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithCriteriaR4Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaR4Test.java index dfe9b283c9f..ad34b30e238 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithCriteriaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaR4Test.java @@ -1,6 +1,7 @@ -package ca.uhn.fhir.jpa.subscription.r4; +package ca.uhn.fhir.jpa.subscription.websocket; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; +import ca.uhn.fhir.jpa.subscription.FhirR4Util; import ca.uhn.fhir.jpa.subscription.SocketImplementation; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; @@ -19,7 +20,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; // This is currently disabled as the criteria mechanism was a non-standard experiment diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdDstu2Test.java similarity index 92% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu2Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdDstu2Test.java index 7ba7a7d8366..4997528a8e5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdDstu2Test.java @@ -1,7 +1,9 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.websocket; import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test; -import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; +import ca.uhn.fhir.jpa.subscription.FhirDstu2Util; +import ca.uhn.fhir.jpa.subscription.SocketImplementation; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; @@ -21,13 +23,14 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; import java.net.URI; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; /** * Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the @@ -52,11 +55,13 @@ public class WebsocketWithSubscriptionIdDstu2Test extends BaseResourceProviderDs private WebSocketClient myWebSocketClient; private SocketImplementation mySocketImplementation; + @Autowired + private SubscriptionTestUtil mySubscriptionTestUtil; + @After public void after() throws Exception { super.after(); - SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); - ourRestServer.unregisterInterceptor(interceptor); + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); } @After @@ -69,8 +74,7 @@ public class WebsocketWithSubscriptionIdDstu2Test extends BaseResourceProviderDs public void before() throws Exception { super.before(); - SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); - ourRestServer.registerInterceptor(interceptor); + mySubscriptionTestUtil.registerWebSocketInterceptor(); /* * Create patient diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdDstu3Test.java similarity index 91% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu3Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdDstu3Test.java index 6ce01a57b2c..5ac36a04baa 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdDstu3Test.java @@ -1,7 +1,9 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.websocket; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; -import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; +import ca.uhn.fhir.jpa.subscription.FhirDstu3Util; +import ca.uhn.fhir.jpa.subscription.SocketImplementation; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; import org.eclipse.jetty.websocket.api.Session; @@ -12,13 +14,14 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; import java.net.URI; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; /** * Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the @@ -46,12 +49,14 @@ public class WebsocketWithSubscriptionIdDstu3Test extends BaseResourceProviderDs private WebSocketClient myWebSocketClient; private SocketImplementation mySocketImplementation; + @Autowired + private SubscriptionTestUtil mySubscriptionTestUtil; + @After public void after() throws Exception { super.after(); - SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); - ourRestServer.unregisterInterceptor(interceptor); + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); } @After @@ -67,8 +72,7 @@ public class WebsocketWithSubscriptionIdDstu3Test extends BaseResourceProviderDs myDaoConfig.setSubscriptionEnabled(true); myDaoConfig.setSubscriptionPollDelay(0L); - SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); - ourRestServer.registerInterceptor(interceptor); + mySubscriptionTestUtil.registerWebSocketInterceptor(); /* * Create patient diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithSubscriptionIdR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdR4Test.java similarity index 91% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithSubscriptionIdR4Test.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdR4Test.java index a815ff10f58..374b72efa37 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithSubscriptionIdR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdR4Test.java @@ -1,8 +1,9 @@ -package ca.uhn.fhir.jpa.subscription.r4; +package ca.uhn.fhir.jpa.subscription.websocket; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; +import ca.uhn.fhir.jpa.subscription.FhirR4Util; import ca.uhn.fhir.jpa.subscription.SocketImplementation; -import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; +import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; import org.eclipse.jetty.websocket.api.Session; @@ -13,13 +14,14 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; import java.net.URI; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; /** * Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the @@ -47,12 +49,14 @@ public class WebsocketWithSubscriptionIdR4Test extends BaseResourceProviderR4Tes private WebSocketClient myWebSocketClient; private SocketImplementation mySocketImplementation; + @Autowired + private SubscriptionTestUtil mySubscriptionTestUtil; + @Override @After public void after() throws Exception { super.after(); - SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); - ourRestServer.unregisterInterceptor(interceptor); + mySubscriptionTestUtil.unregisterSubscriptionInterceptor(); } @After @@ -66,8 +70,7 @@ public class WebsocketWithSubscriptionIdR4Test extends BaseResourceProviderR4Tes public void before() throws Exception { super.before(); - SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); - ourRestServer.registerInterceptor(interceptor); + mySubscriptionTestUtil.registerWebSocketInterceptor(); /* * Create patient diff --git a/hapi-fhir-jpaserver-example/README.md b/hapi-fhir-jpaserver-example/README.md index 8339170e048..5c37386c841 100644 --- a/hapi-fhir-jpaserver-example/README.md +++ b/hapi-fhir-jpaserver-example/README.md @@ -1,4 +1,18 @@ -## Running hapi-fhir-jpaserver-example in Tomcat from IntelliJ +# Unsupported + +This [hapi-fhir-jpaserver-example](https://github.com/jamesagnew/hapi-fhir/tree/master/hapi-fhir-jpaserver-example) project is no longer supported. + + +## Supported JPA Example + +The supported HAPI-FHIR JPA example is available in the [hapi-fhir-jpaserver-starter](https://github.com/hapifhir/hapi-fhir-jpaserver-starter) +project within the [hapifhir](https://github.com/hapifhir) GitHub Organization. + +## Previous Documentation + +Below is the original documentation for this project. Note that this documentation is no longer being updated. + +#### Running hapi-fhir-jpaserver-example in Tomcat from IntelliJ Install Tomcat. @@ -33,7 +47,7 @@ Point your browser (or fiddler, or what have you) to `http://localhost:8080/hapi You should get an empty bundle back. -## Running hapi-fhir-jpaserver-example in a Docker container +#### Running hapi-fhir-jpaserver-example in a Docker container Execute the `build-docker-image.sh` script to build the docker image. diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java index 5a2e5fad948..38506ddd976 100644 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java +++ b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java @@ -1,23 +1,6 @@ package ca.uhn.fhir.jpa.demo; -import java.util.Collection; -import java.util.List; - -import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; -import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor; -import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; -import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.ServletException; - -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Meta; -import org.springframework.web.context.ContextLoaderListener; -import org.springframework.web.context.WebApplicationContext; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.jpa.dao.DaoConfig; @@ -26,12 +9,22 @@ import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; +import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.*; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.ETagSupportEnum; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Meta; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.WebApplicationContext; + +import javax.servlet.ServletException; +import java.util.List; public class JpaServerDemo extends RestfulServer { @@ -134,12 +127,10 @@ public class JpaServerDemo extends RestfulServer { setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); /* - * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java) + * Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes() */ - Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values(); - for (IServerInterceptor interceptor : interceptorBeans) { - this.registerInterceptor(interceptor); - } + SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class); + subscriptionInterceptorLoader.registerInterceptors(); /* * If you are hosting this server at a specific DNS name, the server will try to diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java index 3147a776819..3c802e6cb10 100644 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java +++ b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java @@ -1,16 +1,6 @@ package ca.uhn.fhir.jpa.demo; -import java.util.Collection; -import java.util.List; - -import javax.servlet.ServletException; - -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Meta; -import org.springframework.web.context.ContextLoaderListener; -import org.springframework.web.context.WebApplicationContext; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.jpa.dao.DaoConfig; @@ -20,12 +10,20 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.*; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.ETagSupportEnum; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Meta; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.WebApplicationContext; + +import javax.servlet.ServletException; +import java.util.List; public class JpaServerDemoDstu2 extends RestfulServer { @@ -128,12 +126,10 @@ public class JpaServerDemoDstu2 extends RestfulServer { setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); /* - * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java) + * Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes() */ - Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values(); - for (IServerInterceptor interceptor : interceptorBeans) { - this.registerInterceptor(interceptor); - } + SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class); + subscriptionInterceptorLoader.registerInterceptors(); /* * If you are hosting this server at a specific DNS name, the server will try to diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java index 63534dad81d..63d60abd89e 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java @@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.model.entity; * #L% */ +import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.Subscription; import java.util.Arrays; import java.util.Collections; @@ -53,6 +55,8 @@ public class ModelConfig { private Set myTreatBaseUrlsAsLocal = new HashSet<>(); private Set myTreatReferencesAsLogical = new HashSet<>(DEFAULT_LOGICAL_BASE_URLS); private boolean myDefaultSearchParamsCanBeOverridden = false; + private Set mySupportedSubscriptionTypes = new HashSet<>(); + private String myEmailFromAddress = "noreply@unknown.com"; /** * If set to {@code true} the default search params (i.e. the search parameters that are @@ -297,6 +301,46 @@ public class ModelConfig { return this; } + /** + * This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted + * to the server matching these types will be activated. + * + */ + public ModelConfig addSupportedSubscriptionType(Subscription.SubscriptionChannelType theSubscriptionChannelType) { + mySupportedSubscriptionTypes.add(theSubscriptionChannelType); + return this; + } + + /** + * This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted + * to the server matching these types will be activated. + * + */ + public Set getSupportedSubscriptionTypes() { + return Collections.unmodifiableSet(mySupportedSubscriptionTypes); + } + + @VisibleForTesting + public void clearSupportedSubscriptionTypesForUnitTest() { + mySupportedSubscriptionTypes.clear(); + } + + /** + * If e-mail subscriptions are supported, the From address used when sending e-mails + */ + + public String getEmailFromAddress() { + return myEmailFromAddress; + } + + /** + * If e-mail subscriptions are supported, the From address used when sending e-mails + */ + + public void setEmailFromAddress(String theEmailFromAddress) { + myEmailFromAddress = theEmailFromAddress; + } + private static void validateTreatBaseUrlsAsLocal(String theUrl) { Validate.notBlank(theUrl, "Base URL must not be null or empty"); @@ -309,5 +353,4 @@ public class ModelConfig { } - } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java index 72e35d1db1e..a6b1e6d34ab 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java @@ -38,9 +38,6 @@ import org.hl7.fhir.r4.model.Reference; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.PersistenceContextType; import java.util.Date; import java.util.List; import java.util.Map; @@ -61,9 +58,6 @@ public class ResourceLinkExtractor { @Autowired private ISearchParamExtractor mySearchParamExtractor; - @PersistenceContext(type = PersistenceContextType.TRANSACTION) - protected EntityManager myEntityManager; - public void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver) { String resourceType = theEntity.getResourceType(); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java index cbc00cd2f42..b9f658d22a7 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java @@ -36,7 +36,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.scheduling.annotation.Scheduled; import javax.annotation.PostConstruct; @@ -45,29 +44,20 @@ import java.util.*; import static org.apache.commons.lang3.StringUtils.isBlank; public abstract class BaseSearchParamRegistry implements ISearchParamRegistry { + @Autowired + private ModelConfig myModelConfig; + @Autowired + private ISearchParamProvider mySearchParamProvider; + @Autowired + private FhirContext myFhirContext; private static final int MAX_MANAGED_PARAM_COUNT = 10000; private static final Logger ourLog = LoggerFactory.getLogger(BaseSearchParamRegistry.class); private Map> myBuiltInSearchParams; private volatile Map> myActiveUniqueSearchParams = Collections.emptyMap(); private volatile Map, List>> myActiveParamNamesToUniqueSearchParams = Collections.emptyMap(); - @Autowired - private FhirContext myCtx; private volatile Map> myActiveSearchParams; - @Autowired - private ModelConfig myModelConfig; private volatile long myLastRefresh; - private ISearchParamProvider mySearchParamProvider; - - BaseSearchParamRegistry(ISearchParamProvider theSearchParamProvider) { - super(); - mySearchParamProvider = theSearchParamProvider; - } - - @VisibleForTesting - public void setSearchParamProvider(ISearchParamProvider theSearchParamProvider) { - mySearchParamProvider = theSearchParamProvider; - } @Override public void requestRefresh() { @@ -219,10 +209,10 @@ public abstract class BaseSearchParamRegistry implemen public void postConstruct() { Map> resourceNameToSearchParams = new HashMap<>(); - Set resourceNames = myCtx.getResourceNames(); + Set resourceNames = myFhirContext.getResourceNames(); for (String resourceName : resourceNames) { - RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(resourceName); + RuntimeResourceDefinition nextResDef = myFhirContext.getResourceDefinition(resourceName); String nextResourceName = nextResDef.getName(); HashMap nameToParam = new HashMap<>(); resourceNameToSearchParams.put(nextResourceName, nameToParam); @@ -283,7 +273,7 @@ public abstract class BaseSearchParamRegistry implemen continue; } - for (String nextBaseName : SearchParameterUtil.getBaseAsStrings(myCtx, nextSp)) { + for (String nextBaseName : SearchParameterUtil.getBaseAsStrings(myFhirContext, nextSp)) { if (isBlank(nextBaseName)) { continue; } @@ -348,5 +338,9 @@ public abstract class BaseSearchParamRegistry implemen return getActiveSearchParams(theResourceDef.getName()).values(); } - + @VisibleForTesting + @Override + public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) { + mySearchParamProvider = theSearchParamProvider; + } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamRegistry.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamRegistry.java index 9d44ed27be5..dc3d021dd87 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamRegistry.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamRegistry.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.searchparam.registry; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; +import com.google.common.annotations.VisibleForTesting; import java.util.Collection; import java.util.List; @@ -59,4 +60,7 @@ public interface ISearchParamRegistry { RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName); Collection getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef); + + @VisibleForTesting + void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider); } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu2.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu2.java index 20d7bf3d6a4..3dfb2ffec51 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu2.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu2.java @@ -39,10 +39,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank; public class SearchParamRegistryDstu2 extends BaseSearchParamRegistry { -public SearchParamRegistryDstu2(ISearchParamProvider theSearchParamProvider) { - super(theSearchParamProvider); - } - @Override protected JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) { String name = theNextSp.getCode(); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu3.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu3.java index cb4e6c065aa..192bcd36e8e 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu3.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu3.java @@ -39,10 +39,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank; public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry { - public SearchParamRegistryDstu3(ISearchParamProvider theSearchParamProvider) { - super(theSearchParamProvider); - } - @Override protected JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) { String name = theNextSp.getCode(); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR4.java index 6ecdd680093..3198a9fbf70 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR4.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR4.java @@ -41,10 +41,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank; public class SearchParamRegistryR4 extends BaseSearchParamRegistry { - public SearchParamRegistryR4(ISearchParamProvider theSearchParamProvider) { - super(theSearchParamProvider); - } - @Override protected RuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) { String name = theNextSp.getCode(); @@ -128,6 +124,4 @@ public class SearchParamRegistryR4 extends BaseSearchParamRegistry getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) { return null; } + + @Override + public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) { + // nothing + } }; SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry); diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index 5dfb07771a6..49c0b24cfbf 100644 --- a/hapi-fhir-jpaserver-subscription/pom.xml +++ b/hapi-fhir-jpaserver-subscription/pom.xml @@ -29,6 +29,47 @@ hapi-fhir-validation ${project.version} + + org.springframework + spring-messaging + + + org.springframework + spring-context + + + xml-apis + xml-apis + + + + + org.thymeleaf + thymeleaf + + + org.thymeleaf + thymeleaf-spring5 + + + org.springframework + spring-websocket + + + org.springframework + spring-context-support + + + javax.mail + javax.mail-api + provided + + + javax.servlet + javax.servlet-api + provided + + @@ -46,6 +87,16 @@ mockito-core test + + org.eclipse.jetty + jetty-server + test + + + org.eclipse.jetty + jetty-servlet + test + diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscription.java similarity index 97% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscription.java index d1c561db64c..ce6f0727c12 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscription.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.module; /*- * #%L @@ -54,7 +54,7 @@ public class CanonicalSubscription implements Serializable { @JsonProperty("headers") private List myHeaders; @JsonProperty("channelType") - private Subscription.SubscriptionChannelType myChannelType; + private CanonicalSubscriptionChannelType myChannelType; @JsonProperty("status") private Subscription.SubscriptionStatus myStatus; @JsonProperty("triggerDefinition") @@ -73,11 +73,11 @@ public class CanonicalSubscription implements Serializable { } - public Subscription.SubscriptionChannelType getChannelType() { + public CanonicalSubscriptionChannelType getChannelType() { return myChannelType; } - public void setChannelType(Subscription.SubscriptionChannelType theChannelType) { + public void setChannelType(CanonicalSubscriptionChannelType theChannelType) { myChannelType = theChannelType; } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionChannelType.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionChannelType.java new file mode 100644 index 00000000000..c551d51e633 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionChannelType.java @@ -0,0 +1,115 @@ +package ca.uhn.fhir.jpa.subscription.module; + +import org.hl7.fhir.exceptions.FHIRException; + +public enum CanonicalSubscriptionChannelType { + /** + * The channel is executed by making a post to the URI. If a payload is included, the URL is interpreted as the service base, and an update (PUT) is made. + */ + RESTHOOK, + /** + * The channel is executed by sending a packet across a web socket connection maintained by the client. The URL identifies the websocket, and the client binds to this URL. + */ + WEBSOCKET, + /** + * The channel is executed by sending an email to the email addressed in the URI (which must be a mailto:). + */ + EMAIL, + /** + * The channel is executed by sending an SMS message to the phone number identified in the URL (tel:). + */ + SMS, + /** + * The channel is executed by sending a message (e.g. a Bundle with a MessageHeader resource etc.) to the application identified in the URI. + */ + MESSAGE, + /** + * added to help the parsers with the generic types + */ + NULL; + + public static CanonicalSubscriptionChannelType fromCode(String codeString) throws FHIRException { + if (codeString == null || "".equals(codeString)) + return null; + if ("rest-hook".equals(codeString)) + return RESTHOOK; + if ("websocket".equals(codeString)) + return WEBSOCKET; + if ("email".equals(codeString)) + return EMAIL; + if ("sms".equals(codeString)) + return SMS; + if ("message".equals(codeString)) + return MESSAGE; + else + throw new FHIRException("Unknown SubscriptionChannelType code '" + codeString + "'"); + } + + public String toCode() { + switch (this) { + case RESTHOOK: + return "rest-hook"; + case WEBSOCKET: + return "websocket"; + case EMAIL: + return "email"; + case SMS: + return "sms"; + case MESSAGE: + return "message"; + default: + return "?"; + } + } + + public String getSystem() { + switch (this) { + case RESTHOOK: + return "http://hl7.org/fhir/subscription-channel-type"; + case WEBSOCKET: + return "http://hl7.org/fhir/subscription-channel-type"; + case EMAIL: + return "http://hl7.org/fhir/subscription-channel-type"; + case SMS: + return "http://hl7.org/fhir/subscription-channel-type"; + case MESSAGE: + return "http://hl7.org/fhir/subscription-channel-type"; + default: + return "?"; + } + } + + public String getDefinition() { + switch (this) { + case RESTHOOK: + return "The channel is executed by making a post to the URI. If a payload is included, the URL is interpreted as the service base, and an update (PUT) is made."; + case WEBSOCKET: + return "The channel is executed by sending a packet across a web socket connection maintained by the client. The URL identifies the websocket, and the client binds to this URL."; + case EMAIL: + return "The channel is executed by sending an email to the email addressed in the URI (which must be a mailto:)."; + case SMS: + return "The channel is executed by sending an SMS message to the phone number identified in the URL (tel:)."; + case MESSAGE: + return "The channel is executed by sending a message (e.g. a Bundle with a MessageHeader resource etc.) to the application identified in the URI."; + default: + return "?"; + } + } + + public String getDisplay() { + switch (this) { + case RESTHOOK: + return "Rest Hook"; + case WEBSOCKET: + return "Websocket"; + case EMAIL: + return "Email"; + case SMS: + return "SMS"; + case MESSAGE: + return "Message"; + default: + return "?"; + } + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/LinkedBlockingQueueSubscriptionChannel.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/LinkedBlockingQueueSubscriptionChannel.java new file mode 100644 index 00000000000..5178f0f8290 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/LinkedBlockingQueueSubscriptionChannel.java @@ -0,0 +1,12 @@ +package ca.uhn.fhir.jpa.subscription.module; + +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants; + +import java.util.concurrent.LinkedBlockingQueue; + +public class LinkedBlockingQueueSubscriptionChannel extends SubscriptionChannel { + + public LinkedBlockingQueueSubscriptionChannel(String theThreadNamingPattern) { + super(new LinkedBlockingQueue<>(SubscriptionConstants.DELIVERY_EXECUTOR_QUEUE_SIZE), theThreadNamingPattern); + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceModifiedMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java similarity index 89% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceModifiedMessage.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java index c02bae8cf16..4560750cf2a 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceModifiedMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.module; /*- * #%L @@ -53,6 +53,18 @@ public class ResourceModifiedMessage { @JsonIgnore private transient IBaseResource myPayloadDecoded; + // For JSON + public ResourceModifiedMessage() { + } + + public ResourceModifiedMessage(FhirContext theFhirContext, IBaseResource theResource, OperationTypeEnum theOperationType) { + setId(theResource.getIdElement()); + setOperationType(theOperationType); + if (theOperationType != OperationTypeEnum.DELETE) { + setNewPayload(theFhirContext, theResource); + } + } + public String getPayloadId() { return myPayloadId; } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionChannel.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionChannel.java new file mode 100644 index 00000000000..e15aab79679 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionChannel.java @@ -0,0 +1,83 @@ +package ca.uhn.fhir.jpa.subscription.module; + +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants; +import ca.uhn.fhir.util.StopWatch; +import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.SubscribableChannel; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.messaging.support.ExecutorSubscribableChannel; + +import java.util.ArrayList; +import java.util.concurrent.*; + +public class SubscriptionChannel implements SubscribableChannel { + private Logger ourLog = LoggerFactory.getLogger(SubscriptionChannel.class); + + private final ExecutorSubscribableChannel mySubscribableChannel; + private final BlockingQueue myQueue; + + public SubscriptionChannel(BlockingQueue theQueue, String theThreadNamingPattern) { + + ThreadFactory threadFactory = new BasicThreadFactory.Builder() + .namingPattern(theThreadNamingPattern) + .daemon(false) + .priority(Thread.NORM_PRIORITY) + .build(); + RejectedExecutionHandler rejectedExecutionHandler = (theRunnable, theExecutor) -> { + ourLog.info("Note: Executor queue is full ({} elements), waiting for a slot to become available!", theQueue.size()); + StopWatch sw = new StopWatch(); + try { + theQueue.put(theRunnable); + } catch (InterruptedException theE) { + throw new RejectedExecutionException("Task " + theRunnable.toString() + + " rejected from " + theE.toString()); + } + ourLog.info("Slot become available after {}ms", sw.getMillis()); + }; + ThreadPoolExecutor executor = new ThreadPoolExecutor( + 1, + SubscriptionConstants.EXECUTOR_THREAD_COUNT, + 0L, + TimeUnit.MILLISECONDS, + theQueue, + threadFactory, + rejectedExecutionHandler); + myQueue = theQueue; + mySubscribableChannel = new ExecutorSubscribableChannel(executor); + } + + @Override + public boolean subscribe(MessageHandler handler) { + return mySubscribableChannel.subscribe(handler); + } + + @Override + public boolean unsubscribe(MessageHandler handler) { + return mySubscribableChannel.unsubscribe(handler); + } + + @Override + public boolean send(Message message, long timeout) { + return mySubscribableChannel.send(message, timeout); + } + + @VisibleForTesting + public void clearInterceptorsForUnitTest() { + mySubscribableChannel.setInterceptors(new ArrayList<>()); + } + + @VisibleForTesting + public void addInterceptorForUnitTest(ChannelInterceptor theInterceptor) { + mySubscribableChannel.addInterceptor(theInterceptor); + } + + @VisibleForTesting + public int getQueueSizeForUnitTest() { + return myQueue.size(); + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscription.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscription.java new file mode 100644 index 00000000000..17be19e242c --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscription.java @@ -0,0 +1,73 @@ +package ca.uhn.fhir.jpa.subscription.module.cache; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; +import com.google.common.annotations.VisibleForTesting; +import org.hl7.fhir.instance.model.api.IIdType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.SubscribableChannel; + +import java.util.Collection; +import java.util.HashSet; + +public class ActiveSubscription { + private static final Logger ourLog = LoggerFactory.getLogger(ActiveSubscription.class); + + private final CanonicalSubscription mySubscription; + private final SubscribableChannel mySubscribableChannel; + private final Collection myDeliveryHandlerSet = new HashSet<>(); + + public ActiveSubscription(CanonicalSubscription theSubscription, SubscribableChannel theSubscribableChannel) { + mySubscription = theSubscription; + mySubscribableChannel = theSubscribableChannel; + } + + public CanonicalSubscription getSubscription() { + return mySubscription; + } + + public SubscribableChannel getSubscribableChannel() { + return mySubscribableChannel; + } + + public void register(MessageHandler theHandler) { + mySubscribableChannel.subscribe(theHandler); + myDeliveryHandlerSet.add(theHandler); + } + + public void unregister(MessageHandler theMessageHandler) { + if (mySubscribableChannel != null) { + mySubscribableChannel.unsubscribe(theMessageHandler); + if (mySubscribableChannel instanceof DisposableBean) { + try { + ((DisposableBean) mySubscribableChannel).destroy(); + } catch (Exception e) { + ourLog.error("Failed to destroy channel bean", e); + } + } + } + + } + + public void unregisterAll() { + for (MessageHandler messageHandler : myDeliveryHandlerSet) { + unregister(messageHandler); + } + } + + public IIdType getIdElement(FhirContext theFhirContext) { + return mySubscription.getIdElement(theFhirContext); + } + + public String getCriteriaString() { + return mySubscription.getCriteriaString(); + } + + @VisibleForTesting + public MessageHandler getDeliveryHandlerForUnitTest() { + return myDeliveryHandlerSet.iterator().next(); + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java new file mode 100644 index 00000000000..84ee747f293 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java @@ -0,0 +1,52 @@ +package ca.uhn.fhir.jpa.subscription.module.cache; + +import org.apache.commons.lang3.Validate; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class ActiveSubscriptionCache { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ActiveSubscriptionCache.class); + + private final Map myCache = new ConcurrentHashMap<>(); + + public ActiveSubscription get(String theIdPart) { + return myCache.get(theIdPart); + } + + public Collection getAll() { + return Collections.unmodifiableCollection(myCache.values()); + } + + public int size() { + return myCache.size(); + } + + public void put(String theSubscriptionId, ActiveSubscription theValue) { + myCache.put(theSubscriptionId, theValue); + } + + public void remove(String theSubscriptionId) { + Validate.notBlank(theSubscriptionId); + + ActiveSubscription activeSubscription = myCache.get(theSubscriptionId); + if (activeSubscription == null) { + return; + } + + activeSubscription.unregisterAll(); + myCache.remove(theSubscriptionId); + } + + public void unregisterAllSubscriptionsNotInCollection(Collection theAllIds) { + for (String next : new ArrayList<>(myCache.keySet())) { + if (!theAllIds.contains(next)) { + ourLog.info("Unregistering Subscription/{}", next); + remove(next); + } + } + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/BlockingQueueSubscriptionChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/BlockingQueueSubscriptionChannelFactory.java new file mode 100644 index 00000000000..94460ef3273 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/BlockingQueueSubscriptionChannelFactory.java @@ -0,0 +1,22 @@ +package ca.uhn.fhir.jpa.subscription.module.cache; + +import ca.uhn.fhir.jpa.subscription.module.SubscriptionChannel; +import ca.uhn.fhir.jpa.subscription.module.LinkedBlockingQueueSubscriptionChannel; + +public class BlockingQueueSubscriptionChannelFactory implements ISubscriptionChannelFactory { + + @Override + public SubscriptionChannel newDeliveryChannel(String theSubscriptionId, String theChannelType) { + String threadName = "subscription-delivery-" + + theChannelType + + "-" + + theSubscriptionId + + "-%d"; + return new LinkedBlockingQueueSubscriptionChannel(threadName); + } + + @Override + public SubscriptionChannel newMatchingChannel(String theChannelName) { + return new LinkedBlockingQueueSubscriptionChannel(theChannelName + "-%d"); + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionChannelFactory.java new file mode 100644 index 00000000000..4c975e9fa94 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionChannelFactory.java @@ -0,0 +1,9 @@ +package ca.uhn.fhir.jpa.subscription.module.cache; + +import org.springframework.messaging.SubscribableChannel; + +public interface ISubscriptionChannelFactory { + SubscribableChannel newDeliveryChannel(String theSubscriptionId, String theChannelType); + + SubscribableChannel newMatchingChannel(String theChannelName); +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionProvider.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionProvider.java new file mode 100644 index 00000000000..e8f10fff09a --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionProvider.java @@ -0,0 +1,11 @@ +package ca.uhn.fhir.jpa.subscription.module.cache; + +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import org.hl7.fhir.instance.model.api.IBaseResource; + +public interface ISubscriptionProvider { + IBundleProvider search(SearchParameterMap theMap); + + boolean loadSubscription(IBaseResource theResource); +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCannonicalizer.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCannonicalizer.java new file mode 100644 index 00000000000..a332e3ab900 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCannonicalizer.java @@ -0,0 +1,149 @@ +package ca.uhn.fhir.jpa.subscription.module.cache; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.IBaseReference; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.Subscription; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class SubscriptionCannonicalizer { + @Autowired + FhirContext myFhirContext; + + public CanonicalSubscription canonicalize(S theSubscription) { + switch (myFhirContext.getVersion().getVersion()) { + case DSTU2: + return canonicalizeDstu2(theSubscription); + case DSTU3: + return canonicalizeDstu3(theSubscription); + case R4: + return canonicalizeR4(theSubscription); + default: + throw new ConfigurationException("Subscription not supported for version: " + myFhirContext.getVersion().getVersion()); + } + } + + protected CanonicalSubscription canonicalizeDstu2(IBaseResource theSubscription) { + ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription; + + CanonicalSubscription retVal = new CanonicalSubscription(); + try { + retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus())); + retVal.setChannelType(CanonicalSubscriptionChannelType.fromCode(subscription.getChannel().getType())); + retVal.setCriteriaString(subscription.getCriteria()); + retVal.setEndpointUrl(subscription.getChannel().getEndpoint()); + retVal.setHeaders(subscription.getChannel().getHeader()); + retVal.setIdElement(subscription.getIdElement()); + retVal.setPayloadString(subscription.getChannel().getPayload()); + } catch (FHIRException theE) { + throw new InternalErrorException(theE); + } + return retVal; + } + + protected CanonicalSubscription canonicalizeDstu3(IBaseResource theSubscription) { + org.hl7.fhir.dstu3.model.Subscription subscription = (org.hl7.fhir.dstu3.model.Subscription) theSubscription; + + CanonicalSubscription retVal = new CanonicalSubscription(); + try { + retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus().toCode())); + retVal.setChannelType(CanonicalSubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode())); + retVal.setCriteriaString(subscription.getCriteria()); + retVal.setEndpointUrl(subscription.getChannel().getEndpoint()); + retVal.setHeaders(subscription.getChannel().getHeader()); + retVal.setIdElement(subscription.getIdElement()); + retVal.setPayloadString(subscription.getChannel().getPayload()); + + if (retVal.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) { + String from; + String subjectTemplate; + String bodyTemplate; + try { + from = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM); + subjectTemplate = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE); + } catch (FHIRException theE) { + throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE); + } + retVal.getEmailDetails().setFrom(from); + retVal.getEmailDetails().setSubjectTemplate(subjectTemplate); + } + + if (retVal.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) { + String stripVersionIds; + String deliverLatestVersion; + try { + stripVersionIds = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS); + deliverLatestVersion = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION); + } catch (FHIRException theE) { + throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE); + } + retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds)); + retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion)); + } + + } catch (FHIRException theE) { + throw new InternalErrorException(theE); + } + return retVal; + } + + protected CanonicalSubscription canonicalizeR4(IBaseResource theSubscription) { + org.hl7.fhir.r4.model.Subscription subscription = (org.hl7.fhir.r4.model.Subscription) theSubscription; + + CanonicalSubscription retVal = new CanonicalSubscription(); + retVal.setStatus(subscription.getStatus()); + retVal.setChannelType(CanonicalSubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode())); + retVal.setCriteriaString(subscription.getCriteria()); + retVal.setEndpointUrl(subscription.getChannel().getEndpoint()); + retVal.setHeaders(subscription.getChannel().getHeader()); + retVal.setIdElement(subscription.getIdElement()); + retVal.setPayloadString(subscription.getChannel().getPayload()); + + if (retVal.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) { + String from; + String subjectTemplate; + try { + from = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM); + subjectTemplate = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE); + } catch (FHIRException theE) { + throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE); + } + retVal.getEmailDetails().setFrom(from); + retVal.getEmailDetails().setSubjectTemplate(subjectTemplate); + } + + if (retVal.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) { + String stripVersionIds; + String deliverLatestVersion; + try { + stripVersionIds = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS); + deliverLatestVersion = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION); + } catch (FHIRException theE) { + throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE); + } + retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds)); + retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion)); + } + + List topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics"); + if (topicExts.size() > 0) { + IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive(); + if (!"EventDefinition".equals(ref.getReferenceElement().getResourceType())) { + throw new PreconditionFailedException("Topic reference must be an EventDefinition"); + } + } + + return retVal; + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java new file mode 100644 index 00000000000..b279eec3d86 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java @@ -0,0 +1,70 @@ +package ca.uhn.fhir.jpa.subscription.module.cache; + +public class SubscriptionConstants { + + /** + *

+ * This extension should be of type string and should be + * placed on the Subscription.channel element + *

+ */ + public static final String EXT_SUBSCRIPTION_EMAIL_FROM = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-from"; + + /** + *

+ * This extension should be of type string and should be + * placed on the Subscription.channel element + *

+ */ + public static final String EXT_SUBSCRIPTION_SUBJECT_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-subject-template"; + + + /** + * This extension URL indicates whether a REST HOOK delivery should + * include the version ID when delivering. + *

+ * This extension should be of type boolean and should be + * placed on the Subscription.channel element. + *

+ */ + public static final String EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids"; + + /** + * This extension URL indicates whether a REST HOOK delivery should + * reload the resource and deliver the latest version always. This + * could be useful for example if a resource which triggers a + * subscription gets updated many times in short succession and there + * is no value in delivering the older versions. + *

+ * Note that if the resource is now deleted, this may cause + * the delivery to be cancelled altogether. + *

+ * + *

+ * This extension should be of type boolean and should be + * placed on the Subscription.channel element. + *

+ */ + public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version"; + + /** + * The number of threads used in subscription channel processing + */ + public static final int EXECUTOR_THREAD_COUNT = 5; + + /** + * The maximum number of subscriptions that can be active at once + */ + + public static final int MAX_SUBSCRIPTION_RESULTS = 1000; + + /** + * The size of the queue used for sending resources to the subscription matching processor + */ + public static final int PROCESSING_EXECUTOR_QUEUE_SIZE = 1000; + + /** + * The size of the queue used by each subscription delivery queue + */ + public static final int DELIVERY_EXECUTOR_QUEUE_SIZE = 1000; +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionDeliveryHandlerFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionDeliveryHandlerFactory.java new file mode 100644 index 00000000000..2f66e3133ef --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionDeliveryHandlerFactory.java @@ -0,0 +1,37 @@ +package ca.uhn.fhir.jpa.subscription.module.cache; + +import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; +import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionDeliveringRestHookSubscriber; +import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender; +import ca.uhn.fhir.jpa.subscription.module.subscriber.email.SubscriptionDeliveringEmailSubscriber; +import org.hl7.fhir.r4.model.Subscription; +import org.springframework.beans.factory.annotation.Lookup; +import org.springframework.messaging.MessageHandler; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +public abstract class SubscriptionDeliveryHandlerFactory { + private IEmailSender myEmailSender; + + @Lookup + protected abstract SubscriptionDeliveringEmailSubscriber getSubscriptionDeliveringEmailSubscriber(IEmailSender myEmailSender); + @Lookup + protected abstract SubscriptionDeliveringRestHookSubscriber getSubscriptionDeliveringRestHookSubscriber(); + + public Optional createDeliveryHandler(CanonicalSubscription theSubscription) { + if (theSubscription.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) { + return Optional.of(getSubscriptionDeliveringEmailSubscriber(myEmailSender)); + } else if (theSubscription.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) { + return Optional.of(getSubscriptionDeliveringRestHookSubscriber()); + } else { + return Optional.empty(); + } + } + + public void setEmailSender(IEmailSender theEmailSender) { + myEmailSender = theEmailSender; + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java new file mode 100644 index 00000000000..2815ab5cfb6 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java @@ -0,0 +1,103 @@ +package ca.uhn.fhir.jpa.subscription.module.cache; + +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.TokenOrListParam; +import ca.uhn.fhir.rest.param.TokenParam; +import com.google.common.annotations.VisibleForTesting; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Semaphore; + + +@Service +@Lazy +public class SubscriptionLoader { + private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionLoader.class); + + @Autowired + private ISubscriptionProvider mySubscriptionProvidor; + @Autowired + private SubscriptionRegistry mySubscriptionRegistry; + + private final Object myInitSubscriptionsLock = new Object(); + private Semaphore myInitSubscriptionsSemaphore = new Semaphore(1); + + @PostConstruct + public void start() { + initSubscriptions(); + } + + /** + * Read the existing subscriptions from the database + */ + @SuppressWarnings("unused") + @Scheduled(fixedDelay = 60000) + public void initSubscriptions() { + if (!myInitSubscriptionsSemaphore.tryAcquire()) { + return; + } + try { + doInitSubscriptions(); + } finally { + myInitSubscriptionsSemaphore.release(); + } + } + + @VisibleForTesting + public int doInitSubscriptionsForUnitTest() { + return doInitSubscriptions(); + } + + private int doInitSubscriptions() { + synchronized (myInitSubscriptionsLock) { + ourLog.debug("Starting init subscriptions"); + SearchParameterMap map = new SearchParameterMap(); + map.add(Subscription.SP_STATUS, new TokenOrListParam() + .addOr(new TokenParam(null, Subscription.SubscriptionStatus.REQUESTED.toCode())) + .addOr(new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode()))); + map.setLoadSynchronousUpTo(SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS); + + IBundleProvider subscriptionBundleList = mySubscriptionProvidor.search(map); + + if (subscriptionBundleList.size() >= SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS) { + ourLog.error("Currently over " + SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded."); + } + + List resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size()); + + Set allIds = new HashSet<>(); + int changesCount = 0; + for (IBaseResource resource : resourceList) { + String nextId = resource.getIdElement().getIdPart(); + allIds.add(nextId); + boolean changed = mySubscriptionProvidor.loadSubscription(resource); + if (changed) { + changesCount++; + } + } + + mySubscriptionRegistry.unregisterAllSubscriptionsNotInCollection(allIds); + ourLog.trace("Finished init subscriptions - found {}", resourceList.size()); + + return changesCount; + } + } + + @VisibleForTesting + public void setSubscriptionProviderForUnitTest(ISubscriptionProvider theSubscriptionProvider) { + mySubscriptionProvidor = theSubscriptionProvider; + } +} + diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistry.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistry.java new file mode 100644 index 00000000000..40896849a8f --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistry.java @@ -0,0 +1,116 @@ +package ca.uhn.fhir.jpa.subscription.module.cache; + +import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.SubscribableChannel; +import org.springframework.stereotype.Component; + +import javax.annotation.PreDestroy; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; + +/** + * + * Cache of active subscriptions. When a new subscription is added to the cache, a new Spring Channel is created + * and a new MessageHandler for that subscription is subscribed to that channel. These subscriptions, channels, and + * handlers are all caches in this registry so they can be removed it the subscription is deleted. + */ + +@Component +public class SubscriptionRegistry { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionRegistry.class); + + @Autowired + SubscriptionCannonicalizer mySubscriptionCanonicalizer; + @Autowired + SubscriptionDeliveryHandlerFactory mySubscriptionDeliveryHandlerFactory; + @Autowired + ISubscriptionChannelFactory mySubscriptionDeliveryChannelFactory; + + private final ActiveSubscriptionCache myActiveSubscriptionCache = new ActiveSubscriptionCache(); + + public ActiveSubscription get(String theIdPart) { + return myActiveSubscriptionCache.get(theIdPart); + } + + public Collection getAll() { + return myActiveSubscriptionCache.getAll(); + } + + private Optional hasSubscription(IIdType theId) { + Validate.notNull(theId); + Validate.notBlank(theId.getIdPart()); + Optional activeSubscription = Optional.ofNullable(myActiveSubscriptionCache.get(theId.getIdPart())); + return activeSubscription.map(ActiveSubscription::getSubscription); + } + + @SuppressWarnings("UnusedReturnValue") + public CanonicalSubscription registerSubscription(IIdType theId, IBaseResource theSubscription) { + Validate.notNull(theId); + String subscriptionId = theId.getIdPart(); + Validate.notBlank(subscriptionId); + Validate.notNull(theSubscription); + + CanonicalSubscription canonicalized = mySubscriptionCanonicalizer.canonicalize(theSubscription); + SubscribableChannel deliveryChannel = mySubscriptionDeliveryChannelFactory.newDeliveryChannel(subscriptionId, canonicalized.getChannelType().toCode().toLowerCase()); + Optional deliveryHandler = mySubscriptionDeliveryHandlerFactory.createDeliveryHandler(canonicalized); + + ActiveSubscription activeSubscription = new ActiveSubscription(canonicalized, deliveryChannel); + myActiveSubscriptionCache.put(subscriptionId, activeSubscription); + + deliveryHandler.ifPresent(handler -> activeSubscription.register(handler)); + + return canonicalized; + } + + public void unregisterSubscription(IIdType theId) { + Validate.notNull(theId); + String subscriptionId = theId.getIdPart(); + myActiveSubscriptionCache.remove(subscriptionId); + } + + @PreDestroy + public void preDestroy() { + unregisterAllSubscriptionsNotInCollection(Collections.emptyList()); + } + + public void unregisterAllSubscriptionsNotInCollection(Collection theAllIds) { + myActiveSubscriptionCache.unregisterAllSubscriptionsNotInCollection(theAllIds); + } + + public synchronized boolean registerSubscriptionUnlessAlreadyRegistered(IBaseResource theSubscription) { + Optional existingSubscription = hasSubscription(theSubscription.getIdElement()); + CanonicalSubscription newSubscription = mySubscriptionCanonicalizer.canonicalize(theSubscription); + + if (existingSubscription.isPresent()) { + if (newSubscription.equals(existingSubscription.get())) { + // No changes + return false; + } + ourLog.info("Updating already-registered active subscription {}", theSubscription.getIdElement().toUnqualified().getValue()); + unregisterSubscription(theSubscription.getIdElement()); + } else { + ourLog.info("Registering active subscription {}", theSubscription.getIdElement().toUnqualified().getValue()); + } + registerSubscription(theSubscription.getIdElement(), theSubscription); + return true; + } + + public boolean unregisterSubscriptionIfRegistered(IBaseResource theSubscription, String theStatusString) { + if (hasSubscription(theSubscription.getIdElement()).isPresent()) { + ourLog.info("Removing {} subscription {}", theStatusString, theSubscription.getIdElement().toUnqualified().getValue()); + unregisterSubscription(theSubscription.getIdElement()); + return true; + } + return false; + } + + public int size() { + return myActiveSubscriptionCache.size(); + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/config/BaseSubscriptionConfig.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java similarity index 67% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/config/BaseSubscriptionConfig.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java index 18548668e27..4e5fdab2e18 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/config/BaseSubscriptionConfig.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.config; +package ca.uhn.fhir.jpa.subscription.module.config; /*- * #%L @@ -21,24 +21,19 @@ package ca.uhn.fhir.jpa.subscription.config; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; -import ca.uhn.fhir.jpa.subscription.FhirClientSearchParamProvider; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import org.springframework.beans.factory.annotation.Autowired; +import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory; +import ca.uhn.fhir.jpa.subscription.module.cache.BlockingQueueSubscriptionChannelFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration -@ComponentScan(basePackages = "ca.uhn.fhir.jpa") +@ComponentScan(basePackages = {"ca.uhn.fhir.jpa.searchparam", "ca.uhn.fhir.jpa.subscription.module"}) public abstract class BaseSubscriptionConfig { - @Autowired - IGenericClient myClient; - public abstract FhirContext fhirContext(); @Bean - protected ISearchParamProvider searchParamProvider() { - return new FhirClientSearchParamProvider(myClient); + public ISubscriptionChannelFactory blockingQueueSubscriptionDeliveryChannelFactory() { + return new BlockingQueueSubscriptionChannelFactory(); } } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/config/BaseSubscriptionDstu3Config.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java similarity index 92% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/config/BaseSubscriptionDstu3Config.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java index 939b92b0893..c1d2f05f21f 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/config/BaseSubscriptionDstu3Config.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.config; +package ca.uhn.fhir.jpa.subscription.module.config; /*- * #%L @@ -32,7 +32,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; // From BaseDstu3Config -public class BaseSubscriptionDstu3Config extends BaseSubscriptionConfig { +public class SubscriptionDstu3Config extends BaseSubscriptionConfig { @Override public FhirContext fhirContext() { return fhirContextDstu3(); @@ -52,7 +52,7 @@ public class BaseSubscriptionDstu3Config extends BaseSubscriptionConfig { @Bean public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryDstu3(searchParamProvider()); + return new SearchParamRegistryDstu3(); } @Bean(autowire = Autowire.BY_TYPE) diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/CriteriaResourceMatcher.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java similarity index 99% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/CriteriaResourceMatcher.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java index aa9d1fc5eaf..045db3507b8 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/CriteriaResourceMatcher.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.matcher; +package ca.uhn.fhir.jpa.subscription.module.matcher; /*- * #%L diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java similarity index 86% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java index 9d645a5b719..d1060308531 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.matcher; +package ca.uhn.fhir.jpa.subscription.module.matcher; /*- * #%L @@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.subscription.matcher; * #L% */ -import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; public interface ISubscriptionMatcher { SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg); diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java similarity index 90% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemory.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java index bb9005c6958..9688d4afa03 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemory.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.matcher; +package ca.uhn.fhir.jpa.subscription.module.matcher; /*- * #%L @@ -26,16 +26,12 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceLinkExtractor; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; -import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -@Service -@Lazy -public class SubscriptionMatcherInMemory implements ISubscriptionMatcher { +public class InMemorySubscriptionMatcher implements ISubscriptionMatcher { @Autowired private FhirContext myContext; diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/InlineResourceLinkResolver.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InlineResourceLinkResolver.java similarity index 97% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/InlineResourceLinkResolver.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InlineResourceLinkResolver.java index 992ab36a074..ac1e8406b7e 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/InlineResourceLinkResolver.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InlineResourceLinkResolver.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.matcher; +package ca.uhn.fhir.jpa.subscription.module.matcher; /*- * #%L diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatchResult.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchResult.java similarity index 97% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatchResult.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchResult.java index 620116fc4d0..2bc970c559a 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatchResult.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchResult.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.matcher; +package ca.uhn.fhir.jpa.subscription.module.matcher; /*- * #%L diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientResourceRetriever.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientResourceRetriever.java new file mode 100644 index 00000000000..b8a9ffa0c5a --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientResourceRetriever.java @@ -0,0 +1,30 @@ +package ca.uhn.fhir.jpa.subscription.module.standalone; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jpa.subscription.module.subscriber.IResourceRetriever; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class FhirClientResourceRetriever implements IResourceRetriever { + private static final Logger ourLog = LoggerFactory.getLogger(FhirClientResourceRetriever.class); + + @Autowired + FhirContext myFhirContext; + @Autowired + IGenericClient myClient; + + @Override + public IBaseResource getResource(IIdType payloadId) throws ResourceGoneException { + RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(payloadId.getResourceType()); + + return myClient.search().forResource(resourceDef.getName()).withIdAndCompartment(payloadId.getIdPart(), payloadId.getResourceType()).execute(); + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProvider.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java similarity index 84% rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProvider.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java index 50b1b5de76c..422d5fd8c1f 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProvider.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.module.standalone; /*- * #%L @@ -32,11 +32,18 @@ import ca.uhn.fhir.rest.server.SimpleBundleProvider; import ca.uhn.fhir.util.BundleUtil; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +@Service public class FhirClientSearchParamProvider implements ISearchParamProvider { + private static final Logger ourLog = LoggerFactory.getLogger(FhirClientSearchParamProvider.class); - private final IGenericClient myClient; + private IGenericClient myClient; + @Autowired public FhirClientSearchParamProvider(IGenericClient theClient) { myClient = theClient; } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSubscriptionProvider.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSubscriptionProvider.java new file mode 100644 index 00000000000..100a91f19fe --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSubscriptionProvider.java @@ -0,0 +1,51 @@ +package ca.uhn.fhir.jpa.subscription.module.standalone; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; +import ca.uhn.fhir.rest.api.CacheControlDirective; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.server.SimpleBundleProvider; +import ca.uhn.fhir.util.BundleUtil; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class FhirClientSubscriptionProvider implements ISubscriptionProvider { + @Autowired + private FhirContext myFhirContext; + @Autowired + private SubscriptionRegistry mySubscriptionRegistry; + + IGenericClient myClient; + + @Autowired + public FhirClientSubscriptionProvider(IGenericClient theClient) { + myClient = theClient; + } + + @Override + public IBundleProvider search(SearchParameterMap theMap) { + FhirContext fhirContext = myClient.getFhirContext(); + + String searchURL = ResourceTypeEnum.SUBSCRIPTION.getCode() + theMap.toNormalizedQueryString(myFhirContext); + + IBaseBundle bundle = myClient + .search() + .byUrl(searchURL) + .cacheControl(new CacheControlDirective().setNoCache(true)) + .execute(); + + return new SimpleBundleProvider(BundleUtil.toListOfResources(fhirContext, bundle)); + } + + @Override + public boolean loadSubscription(IBaseResource theResource) { + return mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(theResource); + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java new file mode 100644 index 00000000000..cc10f0af1d6 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java @@ -0,0 +1,45 @@ +package ca.uhn.fhir.jpa.subscription.module.standalone; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage; +import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionCheckingSubscriber; +import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessagingException; +import org.springframework.stereotype.Service; + +@Service +public class StandaloneSubscriptionMessageHandler implements MessageHandler { + private static final Logger ourLog = LoggerFactory.getLogger(StandaloneSubscriptionMessageHandler.class); + + @Autowired + FhirContext myFhirContext; + @Autowired + SubscriptionCheckingSubscriber mySubscriptionCheckingSubscriber; + @Autowired + SubscriptionRegistry mySubscriptionRegistry; + + @Override + public void handleMessage(Message theMessage) throws MessagingException { + if (!(theMessage instanceof ResourceModifiedJsonMessage)) { + ourLog.warn("Unexpected message payload type: {}", theMessage); + return; + } + ResourceModifiedMessage resourceModifiedMessage = ((ResourceModifiedJsonMessage) theMessage).getPayload(); + IBaseResource resource = resourceModifiedMessage.getNewPayload(myFhirContext); + RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(resource); + + if (resourceDef.getName().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { + mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(resource); + } + mySubscriptionCheckingSubscriber.handleMessage(theMessage); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseJsonMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseJsonMessage.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseJsonMessage.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseJsonMessage.java index b70157ef1ff..2f9afa24ab6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseJsonMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseJsonMessage.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.module.subscriber; /*- * #%L diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDeliverySubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseSubscriptionDeliverySubscriber.java similarity index 62% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDeliverySubscriber.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseSubscriptionDeliverySubscriber.java index e6bc4412614..d22f756b023 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDeliverySubscriber.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseSubscriptionDeliverySubscriber.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.module.subscriber; /*- * #%L @@ -20,23 +20,26 @@ package ca.uhn.fhir.jpa.subscription; * #L% */ -import org.hl7.fhir.r4.model.Subscription; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Scope; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; -import org.springframework.stereotype.Component; -public abstract class BaseSubscriptionDeliverySubscriber extends BaseSubscriptionSubscriber { +public abstract class BaseSubscriptionDeliverySubscriber implements MessageHandler { private static final Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionDeliverySubscriber.class); - public BaseSubscriptionDeliverySubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) { - super(theChannelType, theSubscriptionInterceptor); - } + @Autowired + protected FhirContext myFhirContext; + @Autowired + protected SubscriptionRegistry mySubscriptionRegistry; @Override - public void handleMessage(Message theMessage) throws MessagingException { + public void handleMessage(Message theMessage) throws MessagingException { if (!(theMessage.getPayload() instanceof ResourceDeliveryMessage)) { ourLog.warn("Unexpected payload type: {}", theMessage.getPayload()); return; @@ -46,15 +49,11 @@ public abstract class BaseSubscriptionDeliverySubscriber extends BaseSubscriptio try { ResourceDeliveryMessage msg = (ResourceDeliveryMessage) theMessage.getPayload(); - subscriptionId = msg.getSubscription().getIdElement(getContext()).getValue(); + subscriptionId = msg.getSubscription().getIdElement(myFhirContext).getValue(); - CanonicalSubscription updatedSubscription = (CanonicalSubscription) getSubscriptionInterceptor().getIdToSubscription().get(msg.getSubscription().getIdElement(getContext()).getIdPart()); + ActiveSubscription updatedSubscription = mySubscriptionRegistry.get(msg.getSubscription().getIdElement(myFhirContext).getIdPart()); if (updatedSubscription != null) { - msg.setSubscription(updatedSubscription); - } - - if (!subscriptionTypeApplies(msg.getSubscription())) { - return; + msg.setSubscription(updatedSubscription.getSubscription()); } handleMessage(msg); diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceRetriever.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceRetriever.java new file mode 100644 index 00000000000..1f9436c6c9c --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceRetriever.java @@ -0,0 +1,8 @@ +package ca.uhn.fhir.jpa.subscription.module.subscriber; + +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; + +public interface IResourceRetriever { + IBaseResource getResource(IIdType id); +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceDeliveryJsonMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceDeliveryJsonMessage.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java index 89bd4da618a..a2b12d24e87 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceDeliveryJsonMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.module.subscriber; /*- * #%L diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceDeliveryMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessage.java similarity index 89% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceDeliveryMessage.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessage.java index e4a495d4bc0..19dd1aeeaf6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceDeliveryMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessage.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.module.subscriber; /*- * #%L @@ -21,9 +21,13 @@ package ca.uhn.fhir.jpa.subscription; */ import ca.uhn.fhir.context.FhirContext; -import com.fasterxml.jackson.annotation.*; +import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.Gson; -import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceModifiedJsonMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceModifiedJsonMessage.java similarity index 91% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceModifiedJsonMessage.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceModifiedJsonMessage.java index 6d6b8186089..20f908f9c56 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceModifiedJsonMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceModifiedJsonMessage.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.module.subscriber; /*- * #%L @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.subscription; * #L% */ +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -28,7 +29,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; @JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) public class ResourceModifiedJsonMessage extends BaseJsonMessage { - @JsonProperty("headers") + @JsonProperty("payload") private ResourceModifiedMessage myPayload; /** diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionCheckingSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriber.java similarity index 55% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionCheckingSubscriber.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriber.java index 480759e0f34..efb0d38cdea 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionCheckingSubscriber.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriber.java @@ -1,27 +1,22 @@ -package ca.uhn.fhir.jpa.subscription; +package ca.uhn.fhir.jpa.subscription.module.subscriber; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails; -import ca.uhn.fhir.jpa.searchparam.MatchUrlService; -import ca.uhn.fhir.jpa.subscription.matcher.ISubscriptionMatcher; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; +import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher; import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; -import java.util.List; +import java.util.Collection; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -45,20 +40,16 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #L% */ -@Component -@Scope("prototype") -public class SubscriptionCheckingSubscriber extends BaseSubscriptionSubscriber { +@Service +public class SubscriptionCheckingSubscriber implements MessageHandler { private Logger ourLog = LoggerFactory.getLogger(SubscriptionCheckingSubscriber.class); - private final ISubscriptionMatcher mySubscriptionMatcher; - @Autowired - private MatchUrlService myMatchUrlService; - - public SubscriptionCheckingSubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, ISubscriptionMatcher theSubscriptionMatcher) { - super(theChannelType, theSubscriptionInterceptor); - this.mySubscriptionMatcher = theSubscriptionMatcher; - } + private ISubscriptionMatcher mySubscriptionMatcher; + @Autowired + private FhirContext myFhirContext; + @Autowired + private SubscriptionRegistry mySubscriptionRegistry; @Override public void handleMessage(Message theMessage) throws MessagingException { @@ -82,17 +73,17 @@ public class SubscriptionCheckingSubscriber extends BaseSubscriptionSubscriber { return; } - IIdType id = msg.getId(getContext()); + IIdType id = msg.getId(myFhirContext); String resourceType = id.getResourceType(); - List subscriptions = getSubscriptionInterceptor().getRegisteredSubscriptions(); + Collection subscriptions = mySubscriptionRegistry.getAll(); ourLog.trace("Testing {} subscriptions for applicability", subscriptions.size()); - for (CanonicalSubscription nextSubscription : subscriptions) { + for (ActiveSubscription nextActiveSubscription : subscriptions) { - String nextSubscriptionId = nextSubscription.getIdElement(getContext()).toUnqualifiedVersionless().getValue(); - String nextCriteriaString = nextSubscription.getCriteriaString(); + String nextSubscriptionId = nextActiveSubscription.getIdElement(myFhirContext).toUnqualifiedVersionless().getValue(); + String nextCriteriaString = nextActiveSubscription.getCriteriaString(); if (isNotBlank(msg.getSubscriptionId())) { if (!msg.getSubscriptionId().equals(nextSubscriptionId)) { @@ -125,45 +116,18 @@ public class SubscriptionCheckingSubscriber extends BaseSubscriptionSubscriber { ourLog.debug("Found match: queueing rest-hook notification for resource: {}", id.toUnqualifiedVersionless().getValue()); ResourceDeliveryMessage deliveryMsg = new ResourceDeliveryMessage(); - deliveryMsg.setPayload(getContext(), msg.getNewPayload(getContext())); - deliveryMsg.setSubscription(nextSubscription); + deliveryMsg.setPayload(myFhirContext, msg.getNewPayload(myFhirContext)); + deliveryMsg.setSubscription(nextActiveSubscription.getSubscription()); deliveryMsg.setOperationType(msg.getOperationType()); - deliveryMsg.setPayloadId(msg.getId(getContext())); + deliveryMsg.setPayloadId(msg.getId(myFhirContext)); ResourceDeliveryJsonMessage wrappedMsg = new ResourceDeliveryJsonMessage(deliveryMsg); - MessageChannel deliveryChannel = getSubscriptionInterceptor().getDeliveryChannel(nextSubscription); + MessageChannel deliveryChannel = nextActiveSubscription.getSubscribableChannel(); if (deliveryChannel != null) { deliveryChannel.send(wrappedMsg); } else { - ourLog.warn("Do not have deliovery channel for subscription {}", nextSubscription.getIdElement(getContext())); + ourLog.warn("Do not have deliovery channel for subscription {}", nextActiveSubscription.getIdElement(myFhirContext)); } } - - } - - /** - * Subclasses may override - */ - protected String massageCriteria(String theCriteria) { - return theCriteria; - } - - /** - * Search based on a query criteria - */ - protected IBundleProvider performSearch(String theCriteria) { - RuntimeResourceDefinition responseResourceDef = getSubscriptionDao().validateCriteriaAndReturnResourceDefinition(theCriteria); - SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(theCriteria, responseResourceDef); - - RequestDetails req = new ServletSubRequestDetails(); - req.setSubRequest(true); - - IFhirResourceDao responseDao = getSubscriptionInterceptor().getDao(responseResourceDef.getImplementingClass()); - responseCriteriaUrl.setLoadSynchronousUpTo(1); - - IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req); - return responseResults; - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionDeliveringRestHookSubscriber.java similarity index 79% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionDeliveringRestHookSubscriber.java index dd34f1d5abd..d31121e0065 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionDeliveringRestHookSubscriber.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.resthook; +package ca.uhn.fhir.jpa.subscription.module.subscriber; /*- * #%L @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.subscription.resthook; * 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. @@ -20,13 +20,7 @@ package ca.uhn.fhir.jpa.subscription.resthook; * #L% */ -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.subscription.BaseSubscriptionDeliverySubscriber; -import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor; -import ca.uhn.fhir.jpa.subscription.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.client.api.*; @@ -36,9 +30,9 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.messaging.MessagingException; import org.springframework.stereotype.Component; @@ -56,9 +50,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDeliverySubscriber { private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringRestHookSubscriber.class); - public SubscriptionDeliveringRestHookSubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) { - super(theChannelType, theSubscriptionInterceptor); - } + @Autowired + IResourceRetriever myResourceRetriever; protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, EncodingEnum thePayloadType, IGenericClient theClient) { IBaseResource payloadResource = getAndMassagePayload(theMsg, theSubscription); @@ -105,7 +98,7 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe } break; case DELETE: - operation = theClient.delete().resourceById(theMsg.getPayloadId(getContext())); + operation = theClient.delete().resourceById(theMsg.getPayloadId(myFhirContext)); break; default: ourLog.warn("Ignoring delivery message of type: {}", theMsg.getOperationType()); @@ -116,7 +109,7 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe operation.encoded(thePayloadType); } - ourLog.info("Delivering {} rest-hook payload {} for {}", theMsg.getOperationType(), thePayloadResource.getIdElement().toUnqualified().getValue(), theSubscription.getIdElement(getContext()).toUnqualifiedVersionless().getValue()); + ourLog.info("Delivering {} rest-hook payload {} for {}", theMsg.getOperationType(), thePayloadResource.getIdElement().toUnqualified().getValue(), theSubscription.getIdElement(myFhirContext).toUnqualifiedVersionless().getValue()); try { operation.execute(); @@ -128,16 +121,14 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe } protected IBaseResource getAndMassagePayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription) { - IBaseResource payloadResource = theMsg.getPayload(getContext()); + IBaseResource payloadResource = theMsg.getPayload(myFhirContext); if (payloadResource == null || theSubscription.getRestHookDetails().isDeliverLatestVersion()) { - IIdType payloadId = theMsg.getPayloadId(getContext()); - RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(payloadId.getResourceType()); - IFhirResourceDao dao = getSubscriptionInterceptor().getDao(resourceDef.getImplementingClass()); + IIdType payloadId = theMsg.getPayloadId(myFhirContext); try { - payloadResource = dao.read(payloadId.toVersionless()); + payloadResource = myResourceRetriever.getResource(payloadId.toVersionless()); } catch (ResourceGoneException e) { - ourLog.warn("Resource {} is deleted, not going to deliver for subscription {}", payloadId.toVersionless(), theSubscription.getIdElement(getContext())); + ourLog.warn("Resource {} is deleted, not going to deliver for subscription {}", payloadId.toVersionless(), theSubscription.getIdElement(myFhirContext)); return null; } } @@ -169,10 +160,10 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe } // Create the client request - getContext().getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + myFhirContext.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); IGenericClient client = null; if (isNotBlank(endpointUrl)) { - client = getContext().newRestfulGenericClient(endpointUrl); + client = myFhirContext.newRestfulGenericClient(endpointUrl); // Additional headers specified in the subscription List headers = subscription.getHeaders(); @@ -191,12 +182,11 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe * @param theMsg */ protected void sendNotification(ResourceDeliveryMessage theMsg) { - FhirContext context= getContext(); Map> params = new HashMap(); List
headers = new ArrayList<>(); StringBuilder url = new StringBuilder(theMsg.getSubscription().getEndpointUrl()); - IHttpClient client = context.getRestfulClientFactory().getHttpClient(url, params, "", RequestTypeEnum.POST, headers); - IHttpRequest request = client.createParamRequest(context, params, null); + IHttpClient client = myFhirContext.getRestfulClientFactory().getHttpClient(url, params, "", RequestTypeEnum.POST, headers); + IHttpRequest request = client.createParamRequest(myFhirContext, params, null); try { IHttpResponse response = request.execute(); } catch (IOException e) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketHandler.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionWebsocketHandler.java similarity index 88% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketHandler.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionWebsocketHandler.java index 33c541c780e..f518d42df83 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketHandler.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionWebsocketHandler.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.websocket; +package ca.uhn.fhir.jpa.subscription.module.subscriber; /* * #%L @@ -21,8 +21,8 @@ package ca.uhn.fhir.jpa.subscription.websocket; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.subscription.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.IdType; @@ -32,18 +32,19 @@ import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessagingException; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; -import java.util.Map; -public class SubscriptionWebsocketHandler extends TextWebSocketHandler implements ISubscriptionWebsocketHandler { +public class SubscriptionWebsocketHandler extends TextWebSocketHandler implements WebSocketHandler { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketHandler.class); @Autowired - private SubscriptionWebsocketInterceptor mySubscriptionWebsocketInterceptor; + protected SubscriptionRegistry mySubscriptionRegistry; + @Autowired private FhirContext myCtx; @@ -102,26 +103,24 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement private class BoundStaticSubscipriptionState implements IState, MessageHandler { - private WebSocketSession mySession; - private CanonicalSubscription mySubscription; + private final WebSocketSession mySession; + private final ActiveSubscription myActiveSubscription; - public BoundStaticSubscipriptionState(WebSocketSession theSession, CanonicalSubscription theSubscription) { + public BoundStaticSubscipriptionState(WebSocketSession theSession, ActiveSubscription theActiveSubscription) { mySession = theSession; - mySubscription = theSubscription; + myActiveSubscription = theActiveSubscription; - String subscriptionId = mySubscription.getIdElement(myCtx).getIdPart(); - mySubscriptionWebsocketInterceptor.registerHandler(subscriptionId, this); + theActiveSubscription.register(this); } @Override public void closing() { - String subscriptionId = mySubscription.getIdElement(myCtx).getIdPart(); - mySubscriptionWebsocketInterceptor.unregisterHandler(subscriptionId, this); + myActiveSubscription.unregister(this); } private void deliver() { try { - String payload = "ping " + mySubscription.getIdElement(myCtx).getIdPart(); + String payload = "ping " + myActiveSubscription.getIdElement(myCtx).getIdPart(); ourLog.info("Sending WebSocket message: {}", payload); mySession.sendMessage(new TextMessage(payload)); } catch (IOException e) { @@ -136,7 +135,7 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement } try { ResourceDeliveryMessage msg = (ResourceDeliveryMessage) theMessage.getPayload(); - if (mySubscription.equals(msg.getSubscription())) { + if (myActiveSubscription.getSubscription().equals(msg.getSubscription())) { deliver(); } } catch (Exception e) { @@ -177,9 +176,8 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement } try { - Map idToSubscription = mySubscriptionWebsocketInterceptor.getIdToSubscription(); - CanonicalSubscription subscription = idToSubscription.get(id.getIdPart()); - myState = new BoundStaticSubscipriptionState( theSession, subscription); + ActiveSubscription activeSubscription = mySubscriptionRegistry.get(id.getIdPart()); + myState = new BoundStaticSubscipriptionState( theSession, activeSubscription); } catch (ResourceNotFoundException e) { try { String message = "Invalid bind request - Unknown subscription: " + id.getValue(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/EmailDetails.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/EmailDetails.java similarity index 96% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/EmailDetails.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/EmailDetails.java index 6129ecdc5fc..4f618be7a7a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/EmailDetails.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/EmailDetails.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.email; +package ca.uhn.fhir.jpa.subscription.module.subscriber.email; /*- * #%L diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/IEmailSender.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/IEmailSender.java similarity index 92% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/IEmailSender.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/IEmailSender.java index 55b0d7f5a08..320ffa5fe74 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/IEmailSender.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/IEmailSender.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.email; +package ca.uhn.fhir.jpa.subscription.module.subscriber.email; /*- * #%L diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSender.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/JavaMailEmailSender.java similarity index 98% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSender.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/JavaMailEmailSender.java index c4c101e5cca..233fb64b8a1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSender.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/JavaMailEmailSender.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.email; +package ca.uhn.fhir.jpa.subscription.module.subscriber.email; /*- * #%L diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionDeliveringEmailSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/SubscriptionDeliveringEmailSubscriber.java similarity index 73% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionDeliveringEmailSubscriber.java rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/SubscriptionDeliveringEmailSubscriber.java index cb659226e97..42fe24ad3d1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionDeliveringEmailSubscriber.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/SubscriptionDeliveringEmailSubscriber.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.subscription.email; +package ca.uhn.fhir.jpa.subscription.module.subscriber.email; /*- * #%L @@ -20,13 +20,14 @@ package ca.uhn.fhir.jpa.subscription.email; * #L% */ -import ca.uhn.fhir.jpa.subscription.BaseSubscriptionDeliverySubscriber; -import ca.uhn.fhir.jpa.subscription.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.module.subscriber.BaseSubscriptionDeliverySubscriber; +import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage; import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.r4.model.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -37,16 +38,17 @@ import static org.apache.commons.lang3.StringUtils.*; @Component @Scope("prototype") - public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliverySubscriber { private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringEmailSubscriber.class); - private SubscriptionEmailInterceptor mySubscriptionEmailInterceptor; + @Autowired + private ModelConfig myModelConfig; - public SubscriptionDeliveringEmailSubscriber(Subscription.SubscriptionChannelType theChannelType, SubscriptionEmailInterceptor theSubscriptionEmailInterceptor) { - super(theChannelType, theSubscriptionEmailInterceptor); + private IEmailSender myEmailSender; - mySubscriptionEmailInterceptor = theSubscriptionEmailInterceptor; + @Autowired + public SubscriptionDeliveringEmailSubscriber(IEmailSender theEmailSender) { + myEmailSender = theEmailSender; } @Override @@ -64,7 +66,7 @@ public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliv } } - String from = processEmailAddressUri(defaultString(subscription.getEmailDetails().getFrom(), mySubscriptionEmailInterceptor.getDefaultFromAddress())); + String from = processEmailAddressUri(defaultString(subscription.getEmailDetails().getFrom(), myModelConfig.getEmailFromAddress())); String subjectTemplate = defaultString(subscription.getEmailDetails().getSubjectTemplate(), provideDefaultSubjectTemplate()); EmailDetails details = new EmailDetails(); @@ -72,10 +74,9 @@ public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliv details.setFrom(from); details.setBodyTemplate(subscription.getPayloadString()); details.setSubjectTemplate(subjectTemplate); - details.setSubscription(subscription.getIdElement(getContext())); + details.setSubscription(subscription.getIdElement(myFhirContext)); - IEmailSender emailSender = mySubscriptionEmailInterceptor.getEmailSender(); - emailSender.send(details); + myEmailSender.send(details); } private String processEmailAddressUri(String next) { @@ -86,8 +87,11 @@ public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliv return next; } - private String provideDefaultSubjectTemplate() { return "HAPI FHIR Subscriptions"; } + + public void setEmailSender(IEmailSender theEmailSender) { + myEmailSender = theEmailSender; + } } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDstu3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDstu3Test.java deleted file mode 100644 index b06f3434592..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDstu3Test.java +++ /dev/null @@ -1,8 +0,0 @@ -package ca.uhn.fhir.jpa.subscription; - -import ca.uhn.fhir.jpa.subscription.config.TestSubscriptionDstu3Config; -import org.springframework.test.context.ContextConfiguration; - -@ContextConfiguration(classes = {TestSubscriptionDstu3Config.class}) -public abstract class BaseSubscriptionDstu3Test extends BaseSubscriptionTest { -} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionTest.java deleted file mode 100644 index c62a44c766e..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package ca.uhn.fhir.jpa.subscription; - -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.subscription.config.MockSearchParamProvider; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -@RunWith(SpringJUnit4ClassRunner.class) -public abstract class BaseSubscriptionTest { - - @Autowired - ISearchParamProvider mySearchParamProvider; - - @Autowired - ISearchParamRegistry mySearchParamRegistry; - - public void setSearchParamBundleResponse(IBundleProvider theBundleProvider) { - ((MockSearchParamProvider)mySearchParamProvider).setBundleProvider(theBundleProvider); - mySearchParamRegistry.forceRefresh(); - } - - -} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionDstu3Config.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionDstu3Config.java deleted file mode 100644 index be4dc5d128b..00000000000 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionDstu3Config.java +++ /dev/null @@ -1,25 +0,0 @@ -package ca.uhn.fhir.jpa.subscription.config; - -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu3; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -@Configuration -@Import(TestSubscriptionConfig.class) -public class TestSubscriptionDstu3Config extends BaseSubscriptionDstu3Config { - @Bean - @Override - public ISearchParamProvider searchParamProvider() { - return new MockSearchParamProvider(); - } - - @Bean - @Override - public ISearchParamRegistry searchParamRegistry() { - return new SearchParamRegistryDstu3(searchParamProvider()); - } - -} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionDstu3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionDstu3Test.java new file mode 100644 index 00000000000..8ae2afbc84e --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionDstu3Test.java @@ -0,0 +1,40 @@ +package ca.uhn.fhir.jpa.subscription.module; + +import ca.uhn.fhir.jpa.subscription.module.config.TestSubscriptionDstu3Config; +import ca.uhn.fhir.util.StopWatch; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.test.context.ContextConfiguration; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.fail; + +@ContextConfiguration(classes = {TestSubscriptionDstu3Config.class}) +public abstract class BaseSubscriptionDstu3Test extends BaseSubscriptionTest { + public static void waitForSize(int theTarget, List theList) { + StopWatch sw = new StopWatch(); + while (theList.size() != theTarget && sw.getMillis() <= 16000) { + try { + Thread.sleep(50); + } catch (InterruptedException theE) { + throw new Error(theE); + } + } + if (sw.getMillis() >= 16000) { + String describeResults = theList + .stream() + .map(t -> { + if (t == null) { + return "null"; + } + if (t instanceof IBaseResource) { + return ((IBaseResource) t).getIdElement().getValue(); + } + return t.toString(); + }) + .collect(Collectors.joining(", ")); + fail("Size " + theList.size() + " is != target " + theTarget + " - Got: " + describeResults); + } + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java new file mode 100644 index 00000000000..e81033463ca --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java @@ -0,0 +1,39 @@ +package ca.uhn.fhir.jpa.subscription.module; + +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider; +import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader; +import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSearchParamProvider; +import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSubscriptionProvider; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +public abstract class BaseSubscriptionTest { + + @Autowired + ISearchParamProvider mySearchParamProvider; + + @Autowired + ISearchParamRegistry mySearchParamRegistry; + + @Autowired + ISubscriptionProvider mySubscriptionProvider; + + @Autowired + SubscriptionLoader mySubscriptionLoader; + + public void initSearchParamRegistry(IBundleProvider theBundleProvider) { + ((MockFhirClientSearchParamProvider)mySearchParamProvider).setBundleProvider(theBundleProvider); + mySearchParamRegistry.forceRefresh(); + } + + public void initSubscriptionLoader(IBundleProvider theBundleProvider) { + ((MockFhirClientSubscriptionProvider)mySubscriptionProvider).setBundleProvider(theBundleProvider); + mySubscriptionLoader.doInitSubscriptionsForUnitTest(); + } + +} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedTest.java new file mode 100644 index 00000000000..d35365db71b --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedTest.java @@ -0,0 +1,50 @@ +package ca.uhn.fhir.jpa.subscription.module; + +import ca.uhn.fhir.context.FhirContext; +import org.hl7.fhir.r4.model.Organization; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class ResourceModifiedTest { + private FhirContext myFhirContext = FhirContext.forR4(); + + @Test + public void testCreate() { + Organization org = new Organization(); + org.setName("testOrgName"); + org.setId("testOrgId"); + ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, org, ResourceModifiedMessage.OperationTypeEnum.CREATE); + assertEquals(org.getIdElement(), msg.getId(myFhirContext)); + assertEquals(ResourceModifiedMessage.OperationTypeEnum.CREATE, msg.getOperationType()); + Organization decodedOrg = (Organization) msg.getNewPayload(myFhirContext); + assertEquals(org.getId(), decodedOrg.getId()); + assertEquals(org.getName(), decodedOrg.getName()); + } + + @Test + public void testUpdate() { + Organization org = new Organization(); + org.setName("testOrgName"); + org.setId("testOrgId"); + ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, org, ResourceModifiedMessage.OperationTypeEnum.UPDATE); + assertEquals(org.getIdElement(), msg.getId(myFhirContext)); + assertEquals(ResourceModifiedMessage.OperationTypeEnum.UPDATE, msg.getOperationType()); + Organization decodedOrg = (Organization) msg.getNewPayload(myFhirContext); + assertEquals(org.getId(), decodedOrg.getId()); + assertEquals(org.getName(), decodedOrg.getName()); + } + + @Test + public void testDelete() { + Organization org = new Organization(); + org.setName("testOrgName"); + org.setId("testOrgId"); + ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, org, ResourceModifiedMessage.OperationTypeEnum.DELETE); + assertEquals(org.getIdElement(), msg.getId(myFhirContext)); + assertEquals(ResourceModifiedMessage.OperationTypeEnum.DELETE, msg.getOperationType()); + assertNull(msg.getNewPayload(myFhirContext)); + } + +} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/MockSearchParamProvider.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSearchParamProvider.java similarity index 63% rename from hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/MockSearchParamProvider.java rename to hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSearchParamProvider.java index 071e3c24bce..cbc47ae2e23 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/MockSearchParamProvider.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSearchParamProvider.java @@ -1,14 +1,14 @@ -package ca.uhn.fhir.jpa.subscription.config; +package ca.uhn.fhir.jpa.subscription.module.config; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.subscription.FhirClientSearchParamProvider; +import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSearchParamProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.server.SimpleBundleProvider; -public class MockSearchParamProvider extends FhirClientSearchParamProvider { +public class MockFhirClientSearchParamProvider extends FhirClientSearchParamProvider { private IBundleProvider myBundleProvider = new SimpleBundleProvider(); - public MockSearchParamProvider() { + public MockFhirClientSearchParamProvider() { super(null); } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSubscriptionProvider.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSubscriptionProvider.java new file mode 100644 index 00000000000..efe5326da84 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSubscriptionProvider.java @@ -0,0 +1,24 @@ +package ca.uhn.fhir.jpa.subscription.module.config; + +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSubscriptionProvider; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.server.SimpleBundleProvider; + +public class MockFhirClientSubscriptionProvider extends FhirClientSubscriptionProvider { + private IBundleProvider myBundleProvider = new SimpleBundleProvider(); + + + public MockFhirClientSubscriptionProvider() { + super(null); + } + + public void setBundleProvider(IBundleProvider theBundleProvider) { + myBundleProvider = theBundleProvider; + } + + @Override + public IBundleProvider search(SearchParameterMap theParams) { + return myBundleProvider; + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionConfig.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionConfig.java similarity index 76% rename from hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionConfig.java rename to hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionConfig.java index a974094f491..a2f4e2cb4cb 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionConfig.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionConfig.java @@ -1,7 +1,8 @@ -package ca.uhn.fhir.jpa.subscription.config; +package ca.uhn.fhir.jpa.subscription.module.config; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.util.PortUtil; import org.springframework.beans.factory.annotation.Autowired; @@ -28,4 +29,9 @@ public class TestSubscriptionConfig { return myFhirContext.newRestfulGenericClient(ourServerBase); }; + + @Bean + public InMemorySubscriptionMatcher inMemorySubscriptionMatcher() { + return new InMemorySubscriptionMatcher(); + } } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionDstu3Config.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionDstu3Config.java new file mode 100644 index 00000000000..883fa7c5233 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionDstu3Config.java @@ -0,0 +1,22 @@ +package ca.uhn.fhir.jpa.subscription.module.config; + +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; +import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; + +@Configuration +@Import(TestSubscriptionConfig.class) +public class TestSubscriptionDstu3Config extends SubscriptionDstu3Config { + @Bean + @Primary + public ISearchParamProvider searchParamProvider() { + return new MockFhirClientSearchParamProvider(); + } + + @Bean + @Primary + public ISubscriptionProvider subsriptionProvider() { return new MockFhirClientSubscriptionProvider();} +} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemoryTestR3.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherTestR3.java similarity index 96% rename from hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemoryTestR3.java rename to hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherTestR3.java index ca60086a3ab..4b51e775359 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemoryTestR3.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherTestR3.java @@ -1,6 +1,6 @@ -package ca.uhn.fhir.jpa.subscription.matcher; +package ca.uhn.fhir.jpa.subscription.module.matcher; -import ca.uhn.fhir.jpa.subscription.BaseSubscriptionDstu3Test; +import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.server.SimpleBundleProvider; import org.hl7.fhir.dstu3.model.*; @@ -15,23 +15,23 @@ import java.util.Arrays; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class SubscriptionMatcherInMemoryTestR3 extends BaseSubscriptionDstu3Test { +public class InMemorySubscriptionMatcherTestR3 extends BaseSubscriptionDstu3Test { @Autowired - SubscriptionMatcherInMemory mySubscriptionMatcherInMemory; + InMemorySubscriptionMatcher myInMemorySubscriptionMatcher; private void assertUnsupported(IBaseResource resource, String criteria) { - assertFalse(mySubscriptionMatcherInMemory.match(criteria, resource).supported()); + assertFalse(myInMemorySubscriptionMatcher.match(criteria, resource).supported()); } private void assertMatched(IBaseResource resource, String criteria) { - SubscriptionMatchResult result = mySubscriptionMatcherInMemory.match(criteria, resource); + SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, resource); assertTrue(result.supported()); assertTrue(result.matched()); } private void assertNotMatched(IBaseResource resource, String criteria) { - SubscriptionMatchResult result = mySubscriptionMatcherInMemory.match(criteria, resource); + SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, resource); assertTrue(result.supported()); assertFalse(result.matched()); @@ -282,7 +282,7 @@ public class SubscriptionMatcherInMemoryTestR3 extends BaseSubscriptionDstu3Test sp.setStatus(Enumerations.PublicationStatus.ACTIVE); IBundleProvider bundle = new SimpleBundleProvider(Arrays.asList(sp), "uuid"); - setSearchParamBundleResponse(bundle); + initSearchParamRegistry(bundle); { Provenance prov = new Provenance(); @@ -314,7 +314,7 @@ public class SubscriptionMatcherInMemoryTestR3 extends BaseSubscriptionDstu3Test sp.setStatus(Enumerations.PublicationStatus.ACTIVE); IBundleProvider bundle = new SimpleBundleProvider(Arrays.asList(sp), "uuid"); - setSearchParamBundleResponse(bundle); + initSearchParamRegistry(bundle); { BodySite bodySite = new BodySite(); @@ -406,7 +406,7 @@ public class SubscriptionMatcherInMemoryTestR3 extends BaseSubscriptionDstu3Test sp.setStatus(Enumerations.PublicationStatus.ACTIVE); IBundleProvider bundle = new SimpleBundleProvider(Arrays.asList(sp), "uuid"); - setSearchParamBundleResponse(bundle); + initSearchParamRegistry(bundle); { ProcedureRequest pr = new ProcedureRequest(); diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseSubscriptionChannelDstu3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseSubscriptionChannelDstu3Test.java new file mode 100644 index 00000000000..0c4555ef824 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseSubscriptionChannelDstu3Test.java @@ -0,0 +1,175 @@ +package ca.uhn.fhir.jpa.subscription.module.standalone; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test; +import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage; +import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory; +import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage; +import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionCheckingSubscriberTest; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.util.PortUtil; +import com.google.common.collect.Lists; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.SubscribableChannel; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public abstract class BaseSubscriptionChannelDstu3Test extends BaseSubscriptionDstu3Test { + private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionCheckingSubscriberTest.class); + + @Autowired + FhirContext myFhirContext; + @Autowired + StandaloneSubscriptionMessageHandler myStandaloneSubscriptionMessageHandler; + @Autowired + ISubscriptionChannelFactory mySubscriptionDeliveryChannelFactory; + + private static int ourListenerPort; + private static RestfulServer ourListenerRestServer; + private static Server ourListenerServer; + protected static String ourListenerServerBase; + protected static List ourCreatedObservations = Collections.synchronizedList(Lists.newArrayList()); + protected static List ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList()); + protected static List ourContentTypes = Collections.synchronizedList(new ArrayList<>()); + private static SubscribableChannel ourSubscribableChannel; + private List mySubscriptionIds = Collections.synchronizedList(new ArrayList<>()); + private long idCounter = 0; + + @After + public void afterUnregisterRestHookListener() { + mySubscriptionIds.clear(); + } + + @Before + public void beforeReset() { + ourCreatedObservations.clear(); + ourUpdatedObservations.clear(); + ourContentTypes.clear(); + if (ourSubscribableChannel == null) { + ourSubscribableChannel = mySubscriptionDeliveryChannelFactory.newDeliveryChannel("test", Subscription.SubscriptionChannelType.RESTHOOK.toCode().toLowerCase()); + ourSubscribableChannel.subscribe(myStandaloneSubscriptionMessageHandler); + } + } + + public T sendResource(T theResource) { + ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE); + ResourceModifiedJsonMessage message = new ResourceModifiedJsonMessage(msg); + ourSubscribableChannel.send(message); + return theResource; + } + + protected Subscription createSubscription(String theCriteria, String thePayload, String theEndpoint) throws InterruptedException { + Subscription subscription = newSubscription(theCriteria, thePayload, theEndpoint); + + return sendResource(subscription); + } + + protected Subscription newSubscription(String theCriteria, String thePayload, String theEndpoint) { + Subscription subscription = new Subscription(); + subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); + subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED); + subscription.setCriteria(theCriteria); + ++idCounter; + IdType id = new IdType("Subscription", idCounter); + subscription.setId(id); + + Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); + channel.setType(Subscription.SubscriptionChannelType.RESTHOOK); + channel.setPayload(thePayload); + channel.setEndpoint(theEndpoint); + subscription.setChannel(channel); + return subscription; + } + + protected Observation sendObservation(String code, String system) { + Observation observation = new Observation(); + ++idCounter; + IdType id = new IdType("Observation", idCounter); + observation.setId(id); + + CodeableConcept codeableConcept = new CodeableConcept(); + observation.setCode(codeableConcept); + Coding coding = codeableConcept.addCoding(); + coding.setCode(code); + coding.setSystem(system); + + observation.setStatus(Observation.ObservationStatus.FINAL); + + return sendResource(observation); + } + + + @BeforeClass + public static void startListenerServer() throws Exception { + ourListenerPort = PortUtil.findFreePort(); + ourListenerRestServer = new RestfulServer(FhirContext.forDstu3()); + ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context"; + + ObservationListener obsListener = new ObservationListener(); + ourListenerRestServer.setResourceProviders(obsListener); + + ourListenerServer = new Server(ourListenerPort); + + ServletContextHandler proxyHandler = new ServletContextHandler(); + proxyHandler.setContextPath("/"); + + ServletHolder servletHolder = new ServletHolder(); + servletHolder.setServlet(ourListenerRestServer); + proxyHandler.addServlet(servletHolder, "/fhir/context/*"); + + ourListenerServer.setHandler(proxyHandler); + ourListenerServer.start(); + } + + @AfterClass + public static void stopListenerServer() throws Exception { + ourListenerServer.stop(); + } + + public static class ObservationListener implements IResourceProvider { + + @Create + public MethodOutcome create(@ResourceParam Observation theObservation, HttpServletRequest theRequest) { + ourLog.info("Received Listener Create"); + ourContentTypes.add(theRequest.getHeader(ca.uhn.fhir.rest.api.Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", "")); + ourCreatedObservations.add(theObservation); + return new MethodOutcome(new IdType("Observation/1"), true); + } + + @Override + public Class getResourceType() { + return Observation.class; + } + + @Update + public MethodOutcome update(@ResourceParam Observation theObservation, HttpServletRequest theRequest) { + ourUpdatedObservations.add(theObservation); + ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", "")); + ourLog.info("Received Listener Update (now have {} updates)", ourUpdatedObservations.size()); + return new MethodOutcome(new IdType("Observation/1"), false); + } + + } + +} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java new file mode 100644 index 00000000000..d843316de62 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java @@ -0,0 +1,42 @@ +package ca.uhn.fhir.jpa.subscription.module.standalone; + +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.server.SimpleBundleProvider; +import org.hl7.fhir.dstu3.model.Subscription; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class SubscriptionLoaderFhirClientTest extends BaseSubscriptionChannelDstu3Test { + private String myCode = "1000000050"; + + @Before + public void loadSubscriptions() { + String payload = "application/fhir+json"; + + String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml"; + String criteria2 = "Observation?code=SNOMED-CT|" + myCode + "111&_format=xml"; + + + List subs = new ArrayList<>(); + subs.add(newSubscription(criteria1, payload, ourListenerServerBase)); + subs.add(newSubscription(criteria2, payload, ourListenerServerBase)); + + IBundleProvider bundle = new SimpleBundleProvider(new ArrayList<>(subs), "uuid"); + initSubscriptionLoader(bundle); + } + + @Test + public void testSubscriptionLoaderFhirClient() throws Exception { + sendObservation(myCode, "SNOMED-CT"); + + waitForSize(0, ourCreatedObservations); + waitForSize(1, ourUpdatedObservations); + assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0)); + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriberTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriberTest.java new file mode 100644 index 00000000000..ac8d461455a --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriberTest.java @@ -0,0 +1,69 @@ +package ca.uhn.fhir.jpa.subscription.module.subscriber; + +import ca.uhn.fhir.jpa.subscription.module.standalone.BaseSubscriptionChannelDstu3Test; +import ca.uhn.fhir.rest.api.Constants; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.assertEquals; + +/** + * Tests copied from jpa.subscription.resthook.RestHookTestDstu3Test + */ +public class SubscriptionCheckingSubscriberTest extends BaseSubscriptionChannelDstu3Test { + private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionCheckingSubscriberTest.class); + + @Test + public void testRestHookSubscriptionApplicationFhirJson() throws Exception { + String payload = "application/fhir+json"; + + String code = "1000000050"; + String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; + String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml"; + + createSubscription(criteria1, payload, ourListenerServerBase); + createSubscription(criteria2, payload, ourListenerServerBase); + + sendObservation(code, "SNOMED-CT"); + + waitForSize(0, ourCreatedObservations); + waitForSize(1, ourUpdatedObservations); + assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0)); + } + + @Test + public void testRestHookSubscriptionApplicationXmlJson() throws Exception { + String payload = "application/fhir+xml"; + + String code = "1000000050"; + String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; + String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml"; + + createSubscription(criteria1, payload, ourListenerServerBase); + createSubscription(criteria2, payload, ourListenerServerBase); + + sendObservation(code, "SNOMED-CT"); + + waitForSize(0, ourCreatedObservations); + waitForSize(1, ourUpdatedObservations); + assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0)); + } + + @Test + public void testRestHookSubscriptionWithoutPayload() throws Exception { + String payload = ""; + + String code = "1000000050"; + String criteria1 = "Observation?code=SNOMED-CT|" + code; + String criteria2 = "Observation?code=SNOMED-CT|" + code + "111"; + + createSubscription(criteria1, payload, ourListenerServerBase); + createSubscription(criteria2, payload, ourListenerServerBase); + + sendObservation(code, "SNOMED-CT"); + + waitForSize(0, ourCreatedObservations); + waitForSize(0, ourUpdatedObservations); + } +} diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java index f5d2d3d67ce..76c75078361 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java @@ -21,7 +21,6 @@ import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor; import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import ca.uhn.fhirtest.config.*; import ca.uhn.hapi.converters.server.VersionedApiConverterInterceptor; @@ -35,7 +34,6 @@ import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; -import java.util.Collection; import java.util.List; public class TestRestfulServer extends RestfulServer { @@ -225,15 +223,6 @@ public class TestRestfulServer extends RestfulServer { * Spool results to the database */ setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); - - /* - * Load interceptors for the server from Spring - */ - Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values(); - for (IServerInterceptor interceptor : interceptorBeans) { - this.registerInterceptor(interceptor); - } - } /** diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index baf83dca891..15298ad10f3 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -1338,7 +1338,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer rpsDev = (List) ourAppCtx.getBean("myResourceProvidersDstu2", List.class); +// List rpsDev = (List) ourAppCtx.getBean("myResourceProvidersDstu2", List.class); // restServerDstu2.setResourceProviders(rpsDev); // // JpaSystemProviderDstu2 systemProvDev = (JpaSystemProviderDstu2) ourAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class); @@ -74,7 +74,7 @@ public class OverlayTestApp { // restServerDstu1.setPagingProvider(new FifoMemoryPagingProvider(10)); // restServerDstu1.setImplementationDescription("This is a great server!!!!"); // restServerDstu1.setFhirContext(ourAppCtx.getBean("myFhirContextDstu1", FhirContext.class)); -// List rpsDstu1 = (List) ourAppCtx.getBean("myResourceProvidersDstu1", List.class); +// List rpsDstu1 = (List) ourAppCtx.getBean("myResourceProvidersDstu1", List.class); // restServerDstu1.setResourceProviders(rpsDstu1); // // JpaSystemProviderDstu1 systemProvDstu1 = (JpaSystemProviderDstu1) ourAppCtx.getBean("mySystemProviderDstu1", JpaSystemProviderDstu1.class); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.java index bfb1d8d44c9..88f1a24b24e 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.java @@ -9,7 +9,6 @@ import ca.uhn.fhir.validation.IValidatorModule; import com.google.gson.*; import org.apache.commons.lang3.Validate; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; @@ -32,8 +31,8 @@ import javax.xml.parsers.DocumentBuilderFactory; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/InstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/InstanceValidator.java index 668be6e33ae..50e6fff9d89 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/InstanceValidator.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/InstanceValidator.java @@ -21,8 +21,8 @@ import org.hl7.fhir.r4.elementmodel.ParserBase.ValidationPolicy; import org.hl7.fhir.r4.formats.FormatUtilities; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.r4.model.ElementDefinition.*; import org.hl7.fhir.r4.model.Enumeration; +import org.hl7.fhir.r4.model.ElementDefinition.*; import org.hl7.fhir.r4.model.Enumerations.BindingStrength; import org.hl7.fhir.r4.model.ImplementationGuide.ImplementationGuideGlobalComponent; import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemAnswerOptionComponent; diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireValidatorDstu3Test.java index 9852db91793..a85fe71d9b8 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireValidatorDstu3Test.java @@ -1,13 +1,10 @@ package org.hl7.fhir.dstu3.hapi.validation; -import java.util.Collections; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ValidationResult; - import org.hamcrest.Matchers; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.CodeableConcept; @@ -21,6 +18,8 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collections; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/QuestionnaireValidatorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/QuestionnaireValidatorR4Test.java index 61e2b51b791..6c9334bf76e 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/QuestionnaireValidatorR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/QuestionnaireValidatorR4Test.java @@ -5,7 +5,6 @@ import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ValidationResult; - import org.hamcrest.Matchers; import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderSourcesGeneratorMojo.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderSourcesGeneratorMojo.java index 1e53068895b..c93e7fbb41b 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderSourcesGeneratorMojo.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderSourcesGeneratorMojo.java @@ -1,16 +1,13 @@ package ca.uhn.fhir.tinder; -import java.io.*; -import java.util.*; - -import org.apache.maven.plugin.*; -import org.apache.maven.plugins.annotations.*; +import ca.uhn.fhir.tinder.parser.ResourceGeneratorUsingModel; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.project.MavenProject; +import org.apache.maven.plugins.annotations.Parameter; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.tinder.parser.*; +import java.io.File; @Mojo(name = "generate-sources", defaultPhase = LifecyclePhase.GENERATE_SOURCES) public class TinderSourcesGeneratorMojo extends AbstractGeneratorMojo { diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 9f0f39e0bb6..680a82f3f37 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -25,6 +25,23 @@ DaoConfig (by default it is true). If isEnableInMemorySubscriptionMatching is "false", then all subscription matching will query the database as before. + + Removed BaseSubscriptionInterceptor and all its subclasses (RestHook, EMail, WebSocket). These are replaced + by two new interceptors: SubscriptionActivatingInterceptor that is responsible for activating subscriptions + and SubscriptionMatchingInterceptor that is responsible for matching incoming resources against activated + subscriptions. Call DaoConfig.addSupportedSubscriptionType(type) to configure which subscription types + are supported in your environment. The helper method SubscriptionInterceptorLoader.registerInterceptors() + will check if any subscription types are supported, and if so then register both the activating and matching + interceptors. See https://github.com/jamesagnew/hapi-fhir/wiki/Proposed-Subscription-Design-Change for more + details. + + + Added support for matching subscriptions in a separate server from the REST Server. To do this, run the + SubscriptionActivatingInterceptor on the REST server and the SubscriptionMatchingInterceptor in the + standalone server. Classes required to support running a standalone subscription server are in the + ca.uhn.fhir.jpa.subscription.module.standalone package. These classes are excluded by default from + the JPA ApplicationContext (that package is explicitly filtered out in the BaseConfig.java @ComponentScan). + Changed behaviour of FHIR Server to reject subscriptions with invalid criteria. If a Subscription is submitted with invalid criteria, the server returns HTTP 422 "Unprocessable Entity" and the @@ -156,6 +173,37 @@ resources could start reindexing before the new search parameter had been saved, meaning that it was not applied to all resources. This has been corrected. + + In example-projects/README.md and hapi-fhir-jpaserver-example/README.md, incidate that these examples projects + are no longer maintained. The README.md points users to a starter project they should use for examples. + + + Replaced use of BeanFactory with custom factory classes that Spring @Lookup the @Scope("prototype") beans + (e.g. SearchBuilderFactory). + + + Moved e-mail from address configuration from EmailInterceptor (which doesn't exist any more) to DaoConfig. + + + Added 3 interfaces for services required by the standalone subscription server. The standalone subscription + server doesn't have access to a database and so needs to get its resources using a FhirClient. Thus + for each of these interfaces, there are two implementations: a Dao implementaiton and a FhirClient + implementation. The interfaces thus introduced are ISubscriptionProvider (used to load subscriptions + into the SubscriptionRegistry), the IResourceProvider (used to get the latest version of a resource + if the "get latest version" flag is set on the subscription) and ISearchParamProvider used to load + custom search parameters. + + + Separated active subscription cache from the interceptors into a new Spring component called the + SubscriptionRegistry. This component maintains a cache of ActiveSubscriptions. An ActiveSubscription + contains the subscription, it's delivery channel, and a list of delivery handlers. + + + Introduced a Spring factory interfaces called ISubscriptionDeliveryChannelFactory and + ISubscriptionDeliveryHandlerFactory that are used to create delivery channels and handlers. By default, + HAPI FHIR ships with a LinkedBlockingQueue implementation of the delivery channel factory. If a different + type of channel factory is required, add it to your application context and mark it as @Primary. +