From d1b963321a447be745d2a82e218bf02fa3b94f40 Mon Sep 17 00:00:00 2001 From: Tadgh Date: Fri, 22 May 2020 12:03:20 -0700 Subject: [PATCH 1/2] MVP EMPI implementation (#1857) EMPI Initial Implementation (still plenty of known gaps) --- hapi-deployable-pom/pom.xml | 6 +- .../java/ca/uhn/fhir/context/FhirContext.java | 55 +- .../DefaultProfileValidationSupport.java | 2 +- .../context/support/IValidationSupport.java | 2 +- .../java/ca/uhn/fhir/fhirpath/IFhirPath.java | 8 +- .../ca/uhn/fhir/interceptor/api/Pointcut.java | 57 ++ .../narrative2/BaseNarrativeGenerator.java | 2 +- .../java/ca/uhn/fhir/parser/BaseParser.java | 6 +- .../java/ca/uhn/fhir/parser/JsonParser.java | 2 +- .../java/ca/uhn/fhir/parser/ParserState.java | 37 +- .../java/ca/uhn/fhir/parser/RDFParser.java | 2 +- .../java/ca/uhn/fhir/parser/XmlParser.java | 2 +- .../uhn/fhir/rest/param/DateRangeParam.java | 2 - .../java/ca/uhn/fhir/util/FhirTerser.java | 2 +- .../main/java/ca/uhn/fhir}/util/JsonUtil.java | 4 +- .../main/java/ca/uhn/fhir/util/MetaUtil.java | 7 +- .../java/ca/uhn/fhir/util/ParametersUtil.java | 57 ++ .../uhn/fhir/util/ResourceReferenceInfo.java | 2 +- .../ca/uhn/fhir}/util/StringNormalizer.java | 6 +- .../fhir/instance/model/api/IAnyResource.java | 3 + .../model/RequestPartitionIdTest.java | 3 +- hapi-fhir-bom/pom.xml | 10 + .../ca/uhn/fhir/cli/ExampleDataUploader.java | 16 +- .../cli/LoadingValidationSupportDstu2.java | 2 +- .../cli/LoadingValidationSupportDstu3.java | 2 +- .../fhir/cli/LoadingValidationSupportR4.java | 2 +- .../HapiFlywayMigrateDatabaseCommandTest.java | 5 +- ...ashMapResourceProviderConceptMapDstu3.java | 7 +- .../HashMapResourceProviderConceptMapR4.java | 7 +- .../uhn/fhir/jpa/demo/FhirServerConfig.java | 2 +- .../ca/uhn/fhir/jpa/demo/JpaServerDemo.java | 6 +- .../okhttp/client/OkHttpRestfulRequest.java | 20 +- .../fhir/rest/client/impl/GenericClient.java | 10 +- ...turningMethodBindingWithResourceParam.java | 2 +- .../BaseResourceReturningMethodBinding.java | 2 +- .../client/method/DeleteMethodBinding.java | 2 +- .../client/method/HistoryMethodBinding.java | 2 +- .../method/HttpSimpleGetClientInvocation.java | 6 +- .../fhir/rest/client/method/MethodUtil.java | 4 +- .../client/method/OperationMethodBinding.java | 4 +- .../client/method/PatchMethodBinding.java | 2 +- .../ValidateMethodBindingDstu2Plus.java | 3 +- .../hapi/fhir/changelog/5_1_0/changes.yaml | 8 + .../ca/uhn/hapi/fhir/docs/files.properties | 5 + .../uhn/hapi/fhir/docs/images/empi-links.svg | 1 + .../ca/uhn/hapi/fhir/docs/server_jpa/diff.md | 12 +- .../hapi/fhir/docs/server_jpa_empi/empi.md | 158 +++++ .../docs/server_jpa_empi/empi_operations.md | 362 ++++++++++ .../docs/server_jpa_empi/empi_settings.md | 11 + hapi-fhir-jacoco/pom.xml | 10 + .../ca/uhn/fhir/jpa/api/dao/DaoRegistry.java | 18 +- .../api/dao/IFhirResourceDaoEncounter.java | 12 +- .../jpa/api/dao/IFhirResourceDaoValueSet.java | 4 +- .../dao/MetadataKeyCurrentlyReindexing.java | 1 - .../jpa/api/model/DeleteMethodOutcome.java | 4 +- hapi-fhir-jpaserver-base/pom.xml | 103 +-- .../jpa/binstore/BinaryAccessProvider.java | 2 +- .../binstore/BinaryStorageInterceptor.java | 2 +- .../fhir/jpa/bulk/BulkDataExportProvider.java | 2 +- .../fhir/jpa/bulk/BulkDataExportSvcImpl.java | 2 +- .../ca/uhn/fhir/jpa/config/BaseConfig.java | 7 +- .../uhn/fhir/jpa/config/BaseDstu2Config.java | 1 - .../jpa/config/dstu3/BaseDstu3Config.java | 1 - .../uhn/fhir/jpa/config/r4/BaseR4Config.java | 5 +- .../uhn/fhir/jpa/config/r5/BaseR5Config.java | 1 - .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 10 +- .../ca/uhn/fhir/jpa/dao/BaseStorageDao.java | 2 +- .../jpa/dao/BaseTransactionProcessor.java | 4 +- .../ca/uhn/fhir/jpa/dao/EmpiLinkDaoSvc.java | 195 ++++++ .../uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java | 15 +- ...JpaPersistedResourceValidationSupport.java | 2 +- .../uhn/fhir/jpa/dao/data/IEmpiLinkDao.java | 35 + .../dao/expunge/ExpungeEverythingService.java | 39 +- .../fhir/jpa/dao/expunge/ExpungeService.java | 3 - .../dao/index/DaoResourceLinkResolver.java | 2 +- .../fhir/jpa/dao/index/IdHelperService.java | 58 +- .../dao/predicate/PredicateBuilderString.java | 6 +- .../jpa/delete/DeleteConflictService.java | 6 +- .../java/ca/uhn/fhir/jpa/entity/EmpiLink.java | 205 ++++++ .../fhir/jpa/entity/ResourceSearchView.java | 9 +- .../PartitionManagementProvider.java | 2 +- .../partition/RequestPartitionHelperSvc.java | 2 +- .../fhir/jpa/provider/BaseJpaProvider.java | 2 +- .../uhn/fhir/jpa/provider/DiffProvider.java | 3 +- .../provider/JpaConformanceProviderDstu2.java | 2 +- .../SubscriptionTriggeringProvider.java | 2 +- .../dstu3/JpaConformanceProviderDstu3.java | 2 +- .../provider/r4/JpaConformanceProviderR4.java | 2 +- .../provider/r5/JpaConformanceProviderR5.java | 2 +- .../jpa/sched/BaseSchedulerServiceImpl.java | 9 +- .../search/DatabaseBackedPagingProvider.java | 1 - .../jpa/search/SearchCoordinatorSvcImpl.java | 2 +- .../reindex/ResourceReindexingSvcImpl.java | 2 +- .../jpa/search/warm/CacheWarmingSvcImpl.java | 2 +- .../fhir/jpa/term/BaseTermReadSvcImpl.java | 60 +- .../term/TermCodeSystemStorageSvcImpl.java | 14 +- .../uhn/fhir/jpa/term/api/ITermReadSvc.java | 2 +- ...quireManualActivationInterceptorDstu2.java | 4 +- ...quireManualActivationInterceptorDstu3.java | 4 +- ...sRequireManualActivationInterceptorR4.java | 4 +- .../jpa/util/jsonpatch/JsonPatchUtils.java | 2 +- .../jpa/validation/JpaResourceLoader.java | 40 ++ .../jpa/bulk/BulkDataExportProviderTest.java | 12 +- .../ca/uhn/fhir/jpa/config/TestJPAConfig.java | 2 +- .../java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java | 24 +- .../fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java | 35 +- .../dao/dstu2/FhirResourceDaoDstu2Test.java | 97 ++- .../FhirResourceDaoValueSetDstu2Test.java | 31 +- .../fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java | 64 +- .../FhirResourceDaoDocumentDstu3Test.java | 7 +- ...ResourceDaoDstu3ExternalReferenceTest.java | 27 +- .../dao/dstu3/FhirResourceDaoDstu3Test.java | 39 +- .../jpa/dao/dstu3/FhirSystemDaoDstu3Test.java | 23 +- .../fhir/jpa/dao/expunge/ExpungeHookTest.java | 6 +- .../jpa/dao/expunge/PartitionRunnerTest.java | 2 +- .../ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java | 2 +- .../jpa/dao/r4/ConsentEventsDaoR4Test.java | 6 +- .../dao/r4/FhirResourceDaoDocumentR4Test.java | 7 +- .../r4/FhirResourceDaoR4CacheWarmingTest.java | 4 +- .../r4/FhirResourceDaoR4ConceptMapTest.java | 9 +- ...hirResourceDaoR4ExternalReferenceTest.java | 33 +- ...ourceDaoR4SearchCustomSearchParamTest.java | 58 +- .../FhirResourceDaoR4SearchMissingTest.java | 24 +- .../FhirResourceDaoR4SearchOptimizedTest.java | 19 +- ...ourceDaoR4SearchWithElasticSearchTest.java | 4 +- ...urceDaoR4SearchWithLuceneDisabledTest.java | 21 +- .../r4/FhirResourceDaoR4TerminologyTest.java | 29 +- .../dao/r4/FhirResourceDaoR4UpdateTest.java | 41 +- .../dao/r4/FhirResourceDaoR4ValueSetTest.java | 2 +- .../FhirResourceDaoSearchParameterR4Test.java | 2 +- .../fhir/jpa/dao/r4/FhirSystemDaoR4Test.java | 79 ++- .../ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java | 81 ++- .../r5/StorageInterceptorEventsR5Test.java | 2 +- .../delete/DeleteConflictServiceR4Test.java | 6 +- .../jpa/delete/DeleteConflictServiceTest.java | 2 +- .../PartitionManagementProviderTest.java | 2 +- .../BaseResourceProviderDstu2Test.java | 2 +- .../provider/ResourceProviderDstu2Test.java | 116 ++-- .../ResourceProviderExpungeDstu2Test.java | 2 +- ...temProviderTransactionSearchDstu2Test.java | 10 +- .../dstu3/BaseResourceProviderDstu3Test.java | 4 +- .../dstu3/CompositionDocumentDstu3Test.java | 25 +- .../ResourceProviderDstu3ValueSetTest.java | 17 +- .../ResourceProviderExpungeDstu3Test.java | 2 +- ...temProviderTransactionSearchDstu3Test.java | 57 +- .../r4/AuthorizationInterceptorJpaR4Test.java | 2 +- ...BaseMultitenantResourceProviderR4Test.java | 2 +- .../r4/BinaryAccessProviderR4Test.java | 24 +- .../r4/BinaryStorageInterceptorR4Test.java | 14 +- ...sentInterceptorResourceProviderR4Test.java | 22 +- .../jpa/provider/r4/DiffProviderR4Test.java | 4 +- .../jpa/provider/r4/EmptyIndexesR4Test.java | 15 +- .../fhir/jpa/provider/r4/ExpungeR4Test.java | 8 +- .../provider/r4/PatientEverythingR4Test.java | 49 +- ...sourceProviderCustomSearchParamR4Test.java | 13 +- .../r4/ResourceProviderExpungeR4Test.java | 2 +- .../r4/ResourceProviderInterceptorR4Test.java | 24 +- .../provider/r4/ResourceProviderR4Test.java | 132 +++- .../r4/ResourceProviderR4ValueSetTest.java | 14 +- ...SystemProviderTransactionSearchR4Test.java | 25 +- .../r5/BaseResourceProviderR5Test.java | 6 +- .../r5/ResourceProviderR5ValueSetTest.java | 14 +- .../PagingMultinodeProviderDstu3Test.java | 15 +- .../r4/PagingMultinodeProviderR4Test.java | 19 +- .../ResourceReindexingSvcImplTest.java | 2 +- .../fhir/jpa/stresstest/StressTestR4Test.java | 35 +- ...rceptorRegisteredToDaoConfigDstu2Test.java | 7 +- .../SubscriptionTriggeringDstu3Test.java | 2 +- ...minologyLoaderSvcIntegrationDstu3Test.java | 2 +- .../jpa/term/TerminologySvcDeltaR4Test.java | 1 - hapi-fhir-jpaserver-empi/pom.xml | 80 +++ .../jpa/empi/broker/EmpiMessageHandler.java | 139 ++++ .../empi/broker/EmpiQueueConsumerLoader.java | 76 +++ .../jpa/empi/config/EmpiConsumerConfig.java | 203 ++++++ .../config/EmpiSearchParameterLoader.java | 84 +++ .../jpa/empi/config/EmpiSubmitterConfig.java | 41 ++ .../empi/config/EmpiSubscriptionLoader.java | 113 ++++ .../interceptor/EmpiStorageInterceptor.java | 172 +++++ .../EmpiSubmitterInterceptorLoader.java | 60 ++ .../interceptor/IEmpiStorageInterceptor.java | 24 + .../jpa/empi/svc/EmpiCandidateSearchSvc.java | 175 +++++ .../jpa/empi/svc/EmpiEidUpdateService.java | 156 +++++ .../jpa/empi/svc/EmpiLinkQuerySvcImpl.java | 106 +++ .../fhir/jpa/empi/svc/EmpiLinkSvcImpl.java | 174 +++++ .../jpa/empi/svc/EmpiLinkUpdaterSvcImpl.java | 100 +++ .../jpa/empi/svc/EmpiMatchFinderSvcImpl.java | 61 ++ .../fhir/jpa/empi/svc/EmpiMatchLinkSvc.java | 148 +++++ .../jpa/empi/svc/EmpiPersonFindingSvc.java | 182 +++++ .../jpa/empi/svc/EmpiPersonMergerSvcImpl.java | 117 ++++ .../fhir/jpa/empi/svc/EmpiResourceDaoSvc.java | 92 +++ .../fhir/jpa/empi/svc/EmpiSearchParamSvc.java | 58 ++ .../jpa/empi/svc/MatchedPersonCandidate.java | 47 ++ .../ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java | 340 ++++++++++ .../jpa/empi/config/BaseTestEmpiConfig.java | 41 ++ .../jpa/empi/config/TestEmpiConfigR4.java | 9 + .../fhir/jpa/empi/dao/EmpiLinkDaoSvcTest.java | 43 ++ .../fhir/jpa/empi/entity/EmpiEnumTest.java | 20 + .../fhir/jpa/empi/helper/BaseEmpiHelper.java | 110 ++++ .../jpa/empi/helper/EmpiHelperConfig.java | 41 ++ .../fhir/jpa/empi/helper/EmpiHelperR4.java | 75 +++ .../fhir/jpa/empi/helper/EmpiLinkHelper.java | 31 + .../jpa/empi/interceptor/EmpiExpungeTest.java | 80 +++ .../interceptor/EmpiStorageInterceptorIT.java | 244 +++++++ .../jpa/empi/matcher/BasePersonMatcher.java | 79 +++ .../uhn/fhir/jpa/empi/matcher/IsLinkedTo.java | 48 ++ .../jpa/empi/matcher/IsMatchedToAPerson.java | 37 ++ .../empi/matcher/IsPossibleDuplicateOf.java | 61 ++ .../jpa/empi/matcher/IsPossibleLinkedTo.java | 47 ++ .../jpa/empi/matcher/IsPossibleMatchWith.java | 58 ++ .../fhir/jpa/empi/matcher/IsSamePersonAs.java | 46 ++ .../jpa/empi/provider/BaseLinkR4Test.java | 47 ++ .../jpa/empi/provider/BaseProviderR4Test.java | 31 + .../EmpiProviderMergePersonsR4Test.java | 134 ++++ .../provider/EmpiProviderQueryLinkR4Test.java | 98 +++ .../EmpiProviderUpdateLinkR4Test.java | 92 +++ .../empi/searchparam/SearchParameterTest.java | 64 ++ .../empi/svc/EmpiCandidateSearchSvcTest.java | 67 ++ .../fhir/jpa/empi/svc/EmpiLinkSvcTest.java | 104 +++ .../EmpiMatchLinkSvcMultipleEidModeTest.java | 179 +++++ .../jpa/empi/svc/EmpiMatchLinkSvcTest.java | 543 +++++++++++++++ .../jpa/empi/svc/EmpiPersonMergerSvcTest.java | 336 ++++++++++ .../src/test/resources/empi/empi-rules.json | 35 + .../src/test/resources/logback-test.xml | 74 +++ .../uhn/fhir/jpa/migrate/FlywayMigrator.java | 2 - .../uhn/fhir/jpa/migrate/SchemaMigrator.java | 13 +- .../fhir/jpa/migrate/TaskOnlyMigrator.java | 3 - .../tasks/HapiFhirJpaMigrationTasks.java | 153 +++-- .../fhir/jpa/migrate/SchemaMigratorTest.java | 8 +- hapi-fhir-jpaserver-model/pom.xml | 4 + .../ResourceIndexedSearchParamString.java | 4 +- .../ResourceIndexedSearchParamToken.java | 5 +- .../jpa/util/JpaInterceptorBroadcaster.java | 4 +- .../ResourceIndexedSearchParamDateTest.java | 1 - .../jpa/model/entity/ResourceTableTest.java | 2 +- .../jpa/model/util/StringNormalizerTest.java | 7 +- .../fhir/jpa/searchparam/MatchUrlService.java | 1 - .../extractor/BaseSearchParamExtractor.java | 139 +++- .../extractor/ISearchParamExtractor.java | 9 +- .../ResourceIndexedSearchParams.java | 1 - .../SearchParamExtractorService.java | 12 +- .../matcher/IndexedSearchParamExtractor.java | 4 +- .../registry/SearchParamRegistryImpl.java | 2 +- .../SearchParamExtractorDstu3Test.java | 4 +- .../SearchParamExtractorMegaTest.java | 2 +- .../extractor/SearchParamFinder.java | 2 +- .../channel/api/BaseChannelSettings.java | 20 + .../channel/api/ChannelConsumerSettings.java | 8 +- .../channel/api/ChannelProducerSettings.java | 43 ++ .../channel/api/IChannelFactory.java | 24 +- .../channel/api/IChannelReceiver.java | 1 + .../channel/api/IChannelSettings.java | 5 + .../config/SubscriptionChannelConfig.java | 13 +- .../channel/impl/LinkedBlockingChannel.java | 9 +- .../impl/LinkedBlockingChannelFactory.java | 34 +- .../channel/subscription/IChannelNamer.java | 34 + .../SubscriptionChannelFactory.java | 66 +- .../SubscriptionDeliveryHandlerFactory.java | 9 +- .../config/SubscriptionProcessorConfig.java | 7 + ...bscriptionDeliveringMessageSubscriber.java | 121 ++++ ...scriptionDeliveringRestHookSubscriber.java | 4 +- .../SubscriptionWebsocketHandler.java | 6 +- ...mpositeInMemoryDaoSubscriptionMatcher.java | 1 - ...aseSubscriberForSubscriptionResources.java | 2 +- .../SubscriptionMatchingSubscriber.java | 7 +- .../match/registry/SubscriptionConstants.java | 2 +- .../match/registry/SubscriptionLoader.java | 8 +- .../model/ResourceDeliveryMessage.java | 10 + .../model/ResourceModifiedMessage.java | 18 + .../model/config/SubscriptionModelConfig.java | 3 - .../SubscriptionMatcherInterceptor.java | 6 +- .../SubscriptionValidatingInterceptor.java | 30 +- .../SubscriptionTriggeringSvcImpl.java | 4 +- .../SubscriptionChannelFactoryTest.java | 5 +- .../module/BaseSubscriptionTest.java | 11 +- .../module/SubscriptionTestConfig.java | 7 +- .../module/cache/SubscriptionLoaderTest.java | 2 - .../SubscriptionChannelRegistryTest.java | 5 +- ...kingQueueSubscribableChannelDstu3Test.java | 5 - ...SubscriptionValidatingInterceptorTest.java | 157 +++++ hapi-fhir-jpaserver-test-utilities/pom.xml | 61 ++ .../ca/uhn/fhir/jpa/config/TestJpaConfig.java | 54 ++ .../uhn/fhir/jpa/config/TestJpaR4Config.java | 138 ++++ .../ca/uhn/fhir/jpa/test/BaseJpaR4Test.java | 29 + .../ca/uhn/fhir/jpa/test/BaseJpaTest.java | 100 +++ .../ca/uhn/fhirtest/TestRestfulServer.java | 3 +- .../ca/uhn/fhirtest/config/CommonConfig.java | 1 + .../uhn/fhirtest/config/TestDstu2Config.java | 2 +- .../uhn/fhirtest/config/TestDstu3Config.java | 2 +- .../ca/uhn/fhirtest/config/TestR4Config.java | 2 +- .../ca/uhn/fhirtest/config/TestR5Config.java | 2 +- .../java/ca/uhn/fhirtest/UhnFhirTestApp.java | 17 +- hapi-fhir-server-empi/pom.xml | 124 ++++ .../ca/uhn/fhir/empi/api/EmpiConstants.java | 36 + .../uhn/fhir/empi/api/EmpiLinkSourceEnum.java | 36 + .../fhir/empi/api/EmpiMatchResultEnum.java | 45 ++ .../uhn/fhir/empi/api/IEmpiLinkQuerySvc.java | 31 + .../ca/uhn/fhir/empi/api/IEmpiLinkSvc.java | 52 ++ .../fhir/empi/api/IEmpiLinkUpdaterSvc.java | 28 + .../fhir/empi/api/IEmpiMatchFinderSvc.java | 52 ++ .../fhir/empi/api/IEmpiPersonMergerSvc.java | 35 + .../ca/uhn/fhir/empi/api/IEmpiSettings.java | 38 ++ .../ca/uhn/fhir/empi/api/MatchedTarget.java | 50 ++ .../main/java/ca/uhn/fhir/empi/log/Logs.java | 32 + .../ca/uhn/fhir/empi/model/CanonicalEID.java | 139 ++++ .../CanonicalIdentityAssuranceLevel.java | 44 ++ .../empi/model/EmpiTransactionContext.java | 71 ++ .../fhir/empi/provider/BaseEmpiProvider.java | 171 +++++ .../fhir/empi/provider/EmpiProviderDstu3.java | 133 ++++ .../empi/provider/EmpiProviderLoader.java | 64 ++ .../fhir/empi/provider/EmpiProviderR4.java | 134 ++++ .../empi/rules/config/EmpiRuleValidator.java | 44 ++ .../fhir/empi/rules/config/EmpiSettings.java | 103 +++ .../empi/rules/json/DistanceMetricEnum.java | 71 ++ .../empi/rules/json/EmpiFieldMatchJson.java | 91 +++ .../rules/json/EmpiFilterSearchParamJson.java | 81 +++ .../json/EmpiResourceSearchParamJson.java | 52 ++ .../fhir/empi/rules/json/EmpiRulesJson.java | 149 +++++ .../empi/rules/json/VectorMatchResultMap.java | 85 +++ .../EmpiPersonNameMatchModeEnum.java | 28 + .../similarity/HapiStringSimilarity.java | 46 ++ .../similarity/IEmpiFieldSimilarity.java | 31 + .../empi/rules/similarity/NameSimilarity.java | 76 +++ .../similarity/ReferenceMatchSimilarity.java | 32 + .../rules/svc/EmpiResourceComparatorSvc.java | 120 ++++ .../svc/EmpiResourceFieldComparator.java | 96 +++ .../fhir/empi/util/AssuranceLevelUtil.java | 71 ++ .../java/ca/uhn/fhir/empi/util/EIDHelper.java | 114 ++++ .../java/ca/uhn/fhir/empi/util/EmpiUtil.java | 60 ++ .../java/ca/uhn/fhir/empi/util/NameUtil.java | 60 ++ .../ca/uhn/fhir/empi/util/PersonHelper.java | 619 ++++++++++++++++++ .../rest/server/TransactionLogMessages.java | 59 ++ .../java/ca/uhn/fhir/empi/BaseR4Test.java | 89 +++ .../rules/config/EmpiRuleValidatorTest.java | 47 ++ .../empi/rules/json/EmpiRulesJsonR4Test.java | 61 ++ .../rules/json/VectorMatchResultMapTest.java | 24 + .../svc/CustomResourceComparatorR4Test.java | 117 ++++ .../svc/EmpiResourceComparatorSvcR4Test.java | 53 ++ .../EmpiResourceFieldComparatorR4Test.java | 81 +++ .../ca/uhn/fhir/empi/svc/EIDHelperR4Test.java | 78 +++ .../uhn/fhir/empi/svc/NameUtilTestDSTU3.java | 44 ++ .../ca/uhn/fhir/empi/svc/NameUtilTestR4.java | 44 ++ .../fhir/empi/svc/PersonHelperDSTU3Test.java | 84 +++ .../uhn/fhir/empi/svc/PersonHelperR4Test.java | 82 +++ .../empi/util/AssuranceLevelUtilTest.java | 64 ++ .../java/ca/uhn/fhir/empi/util/TestUtils.java | 10 + .../src/test/resources/bad-rules.json | 16 + .../fhir/rest/api/server/RequestDetails.java | 15 +- .../server/SimplePreResourceShowDetails.java | 1 - .../fhir/rest/server/RestfulServerUtils.java | 4 +- .../interceptor/IServerInterceptor.java | 4 +- .../ServeMediaResourceRawInterceptor.java | 2 +- .../interceptor/auth/OperationRule.java | 6 +- .../interceptor/auth/RuleImplConditional.java | 2 +- .../server/interceptor/auth/RuleImplOp.java | 4 +- ...turningMethodBindingWithResourceParam.java | 3 +- .../server/method/HistoryMethodBinding.java | 2 +- .../server/method/OperationMethodBinding.java | 6 +- .../server/method/SearchMethodBinding.java | 2 +- .../provider/HashMapResourceProvider.java | 2 +- .../IResourceProviderFactoryObserver.java | 28 + .../server/provider}/ProviderConstants.java | 27 +- .../provider}/ResourceProviderFactory.java | 17 +- .../autoconfigure/FhirAutoConfiguration.java | 9 +- .../fhir/rest/server/Dstu1BundleFactory.java | 4 +- .../rest/server/Dstu2_1BundleFactory.java | 4 +- .../provider/dstu2/Dstu2BundleFactory.java | 6 +- .../hapi/rest/server/Dstu3BundleFactory.java | 4 +- .../dstu2hl7org/Dstu2Hl7OrgBundleFactory.java | 4 +- .../fhir/r4/hapi/ctx/HapiWorkerContext.java | 3 +- .../r4/hapi/rest/server/R4BundleFactory.java | 4 +- .../fhir/rest/client/GenericClientR4Test.java | 19 +- .../interceptor/ConsentInterceptorTest.java | 21 +- .../uhn/fhir/util/ParametersUtilR4Test.java | 31 +- .../fhir/r5/hapi/ctx/HapiWorkerContext.java | 2 +- .../r5/hapi/rest/server/R5BundleFactory.java | 4 +- .../uhn/test/concurrency/PointcutLatch.java | 14 + .../uhn/fhir/jpa/test/FhirServerConfig.java | 6 +- .../ca/uhn/fhir/jpa/test/OverlayTestApp.java | 31 +- .../fhir-jpabase-spring-test-config.xml | 15 +- ...ToHl7OrgDstu2ValidatingSupportWrapper.java | 2 +- .../VersionSpecificWorkerContextWrapper.java | 4 +- .../resources/vm/jpa_spring_beans_java.vm | 2 +- pom.xml | 9 + 383 files changed, 14148 insertions(+), 935 deletions(-) rename {hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa => hapi-fhir-base/src/main/java/ca/uhn/fhir}/util/JsonUtil.java (97%) rename {hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model => hapi-fhir-base/src/main/java/ca/uhn/fhir}/util/StringNormalizer.java (92%) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/changes.yaml create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/empi-links.svg create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi.md create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_operations.md create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_settings.md create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EmpiLinkDaoSvc.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IEmpiLinkDao.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiLink.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaResourceLoader.java create mode 100644 hapi-fhir-jpaserver-empi/pom.xml create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/broker/EmpiMessageHandler.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/broker/EmpiQueueConsumerLoader.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSearchParameterLoader.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSubmitterConfig.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSubscriptionLoader.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptor.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiSubmitterInterceptorLoader.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/IEmpiStorageInterceptor.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvc.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiEidUpdateService.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkQuerySvcImpl.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcImpl.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkUpdaterSvcImpl.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchFinderSvcImpl.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvc.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonFindingSvc.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcImpl.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiResourceDaoSvc.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiSearchParamSvc.java create mode 100644 hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/MatchedPersonCandidate.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/config/BaseTestEmpiConfig.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/config/TestEmpiConfigR4.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvcTest.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/entity/EmpiEnumTest.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/BaseEmpiHelper.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/EmpiHelperConfig.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/EmpiHelperR4.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/EmpiLinkHelper.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiExpungeTest.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptorIT.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/BasePersonMatcher.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsLinkedTo.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsMatchedToAPerson.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleDuplicateOf.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleLinkedTo.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleMatchWith.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsSamePersonAs.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseLinkR4Test.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseProviderR4Test.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderMergePersonsR4Test.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderQueryLinkR4Test.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderUpdateLinkR4Test.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/searchparam/SearchParameterTest.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvcTest.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcTest.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcMultipleEidModeTest.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcTest.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcTest.java create mode 100644 hapi-fhir-jpaserver-empi/src/test/resources/empi/empi-rules.json create mode 100644 hapi-fhir-jpaserver-empi/src/test/resources/logback-test.xml create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/BaseChannelSettings.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/ChannelProducerSettings.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelSettings.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/IChannelNamer.java create mode 100644 hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/message/SubscriptionDeliveringMessageSubscriber.java create mode 100644 hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptorTest.java create mode 100644 hapi-fhir-jpaserver-test-utilities/pom.xml create mode 100644 hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/config/TestJpaConfig.java create mode 100644 hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/config/TestJpaR4Config.java create mode 100644 hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java create mode 100644 hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaTest.java create mode 100644 hapi-fhir-server-empi/pom.xml create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiConstants.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiLinkSourceEnum.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchResultEnum.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkQuerySvc.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkSvc.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkUpdaterSvc.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiMatchFinderSvc.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiPersonMergerSvc.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiSettings.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/MatchedTarget.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/log/Logs.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/model/CanonicalEID.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/model/CanonicalIdentityAssuranceLevel.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/model/EmpiTransactionContext.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/BaseEmpiProvider.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderLoader.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiRuleValidator.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiSettings.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/DistanceMetricEnum.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiFieldMatchJson.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiFilterSearchParamJson.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiResourceSearchParamJson.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJson.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/VectorMatchResultMap.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/EmpiPersonNameMatchModeEnum.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/HapiStringSimilarity.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/IEmpiFieldSimilarity.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/NameSimilarity.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/ReferenceMatchSimilarity.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceComparatorSvc.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldComparator.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/AssuranceLevelUtil.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/EIDHelper.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/EmpiUtil.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/NameUtil.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/PersonHelper.java create mode 100644 hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/rest/server/TransactionLogMessages.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/BaseR4Test.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/config/EmpiRuleValidatorTest.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJsonR4Test.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/json/VectorMatchResultMapTest.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/CustomResourceComparatorR4Test.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceComparatorSvcR4Test.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldComparatorR4Test.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/EIDHelperR4Test.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/NameUtilTestDSTU3.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/NameUtilTestR4.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/PersonHelperDSTU3Test.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/PersonHelperR4Test.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/util/AssuranceLevelUtilTest.java create mode 100644 hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/util/TestUtils.java create mode 100644 hapi-fhir-server-empi/src/test/resources/bad-rules.json create mode 100644 hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/IResourceProviderFactoryObserver.java rename {hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util => hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider}/ProviderConstants.java (63%) rename {hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/rp => hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider}/ResourceProviderFactory.java (69%) diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index 4affc27d75d..68b1eb46cac 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -1,7 +1,7 @@ 4.0.0 - + ca.uhn.hapi.fhir hapi-fhir 5.1.0-SNAPSHOT @@ -147,6 +147,10 @@ org.apache.poi ooxml-schemas + + net.bytebuddy + byte-buddy + .*\.txt$ diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java index 7458be5edca..a0523e8fec5 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java @@ -27,6 +27,7 @@ import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.VersionUtil; import ca.uhn.fhir.validation.FhirValidator; import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.jena.riot.Lang; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseBundle; @@ -36,8 +37,18 @@ import javax.annotation.Nullable; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; /* * #%L @@ -177,7 +188,12 @@ public class FhirContext { ourLog.info("Creating new FhirContext with auto-detected version [{}]. It is recommended to explicitly select a version for future compatibility by invoking FhirContext.forDstuX()", myVersion.getVersion().name()); } else { - ourLog.info("Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name()); + if ("true".equals(System.getProperty("unit_test_mode"))) { + String calledAt = ExceptionUtils.getStackFrames(new Throwable())[4]; + ourLog.info("Creating new FHIR context for FHIR version [{}]{}", myVersion.getVersion().name(), calledAt); + } else { + ourLog.info("Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name()); + } } myResourceTypesToScan = theResourceTypes; @@ -448,6 +464,36 @@ public class FhirContext { } /** + * Returns the name of a given resource class. + * @param theResourceType + * @return + */ + public String getResourceType(final Class theResourceType) { + return getResourceDefinition(theResourceType).getName(); + } + + /** + * Returns the name of the scanned runtime model for the given type. This is an advanced feature which is generally only needed + * for extending the core library. + */ + public String getResourceType(final IBaseResource theResource) { + return getResourceDefinition(theResource).getName(); + } + + /* + * Returns the type of the scanned runtime model for the given type. This is an advanced feature which is generally only needed + * for extending the core library. + *

+ * Note that this method is case insensitive! + *

+ * + * @throws DataFormatException If the resource name is not known + */ + public String getResourceType(final String theResourceName) throws DataFormatException { + return getResourceDefinition(theResourceName).getName(); + } + + /* * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed * for extending the core library. *

@@ -476,7 +522,6 @@ public class FhirContext { retVal = scanResourceType(clazz); } } - return retVal; } @@ -501,8 +546,10 @@ public class FhirContext { /** * Returns an unmodifiable set containing all resource names known to this * context + * + * @since 5.1.0 */ - public Set getResourceNames() { + public Set getResourceTypes() { Set resourceNames = new HashSet<>(); if (myNameToResourceDefinition.isEmpty()) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java index 3498ac29df0..bf03f54eb1d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java @@ -306,7 +306,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport { List resources = parseBundle(reader); for (IBaseResource next : resources) { - String nextType = getFhirContext().getResourceDefinition(next).getName(); + String nextType = getFhirContext().getResourceType(next); if ("StructureDefinition".equals(nextType)) { String url = getConformanceResourceUrl(next); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java index fc5089e2163..43b7d053875 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java @@ -125,7 +125,7 @@ public interface IValidationSupport { Validate.notNull(theClass, "theClass must not be null or blank"); Validate.notBlank(theUri, "theUri must not be null or blank"); - switch (getFhirContext().getResourceDefinition(theClass).getName()) { + switch (getFhirContext().getResourceType(theClass)) { case "StructureDefinition": return theClass.cast(fetchStructureDefinition(theUri)); case "ValueSet": diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java index a15f716c37f..244de56ecda 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java @@ -20,15 +20,15 @@ package ca.uhn.fhir.fhirpath; * #L% */ +import org.hl7.fhir.instance.model.api.IBase; + import java.util.List; import java.util.Optional; -import org.hl7.fhir.instance.model.api.IBase; - public interface IFhirPath { /** - * Apply the given FluentPath expression against the given input and return + * Apply the given FhirPath expression against the given input and return * all results in a list * * @param theInput The input object (generally a resource or datatype) @@ -38,7 +38,7 @@ public interface IFhirPath { List evaluate(IBase theInput, String thePath, Class theReturnType); /** - * Apply the given FluentPath expression against the given input and return + * Apply the given FhirPath expression against the given input and return * the first match (if any) * * @param theInput The input object (generally a resource or datatype) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java index 672fa91f0d7..0b4e5a10565 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java @@ -757,6 +757,46 @@ public enum Pointcut { */ SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY(boolean.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage"), + /** + * Subscription Hook: + * Invoked immediately after the delivery of MESSAGE subscription. + *

+ * When this hook is called, all processing is complete so this hook should not + * make any changes to the parameters. + *

+ * Hooks may accept the following parameters: + *
    + *
  • ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription
  • + *
  • ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage
  • + *
+ *

+ * Hooks should return void. + *

+ */ + SUBSCRIPTION_AFTER_MESSAGE_DELIVERY(void.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage"), + + /** + * Subscription Hook: + * Invoked immediately before the delivery of a MESSAGE subscription. + *

+ * Hooks may make changes to the delivery payload, or make changes to the + * canonical subscription such as adding headers, modifying the channel + * endpoint, etc. + *

+ * Hooks may accept the following parameters: + *
    + *
  • ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription
  • + *
  • ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage
  • + *
+ *

+ * Hooks may return void or may return a boolean. If the method returns + * void or true, processing will continue normally. If the method + * returns false, processing will be aborted. + *

+ */ + SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY(boolean.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage"), + + /** * Subscription Hook: * Invoked whenever a persisted resource (a resource that has just been stored in the @@ -1522,6 +1562,23 @@ public enum Pointcut { "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails" ), + /** + * EMPI Hook: + * Invoked whenever a persisted Patient/Practitioner resource (a resource that has just been stored in the + * database via a create/update/patch/etc.) has been matched against related resources and EMPI links have been updated. + *

+ * Hooks may accept the following parameters: + *

    + *
  • ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage - This parameter should not be modified as processing is complete when this hook is invoked.
  • + *
  • ca.uhn.fhir.empi.model.TransactionLogMessages - This parameter is for informational messages provided by the EMPI module during EMPI procesing. .
  • + *
+ *

+ *

+ * Hooks should return void. + *

+ */ + EMPI_AFTER_PERSISTED_RESOURCE_CHECKED(void.class, "ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage", "ca.uhn.fhir.rest.server.TransactionLogMessages"), + /** * Performance Tracing Hook: * This hook is invoked when any informational messages generated by the diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/BaseNarrativeGenerator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/BaseNarrativeGenerator.java index c73c545c407..044eb338ae7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/BaseNarrativeGenerator.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/BaseNarrativeGenerator.java @@ -73,7 +73,7 @@ public abstract class BaseNarrativeGenerator implements INarrativeGenerator { } boolean retVal = false; - String resourceName = theFhirContext.getResourceDefinition(theResource).getName(); + String resourceName = theFhirContext.getResourceType(theResource); String contextPath = defaultIfEmpty(theTemplate.getContextPath(), resourceName); // Narrative templates define a path within the resource that they apply to. Here, we're diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index a057299ac43..fb72fe197dc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -445,7 +445,7 @@ public abstract class BaseParser implements IParser { "This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum()); } - String resourceName = myContext.getResourceDefinition(theResource).getName(); + String resourceName = myContext.getResourceType(theResource); theEncodeContext.pushPath(resourceName, true); doEncodeResourceToWriter(theResource, theWriter, theEncodeContext); @@ -995,7 +995,7 @@ public abstract class BaseParser implements IParser { retVal = false; } else { if (myDontEncodeElements != null) { - String resourceName = myContext.getResourceDefinition(theResource).getName(); + String resourceName = myContext.getResourceType(theResource); if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath(resourceName + ".id"))) { retVal = false; } else if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath("*.id"))) { @@ -1020,7 +1020,7 @@ public abstract class BaseParser implements IParser { */ protected boolean shouldEncodePath(IResource theResource, String thePath) { if (myDontEncodeElements != null) { - String resourceName = myContext.getResourceDefinition(theResource).getName(); + String resourceName = myContext.getResourceType(theResource); if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath(resourceName + "." + thePath))) { return false; } else return myDontEncodeElements.stream().noneMatch(t -> t.equalsPath("*." + thePath)); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index 1287002103b..c78290da74f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -625,7 +625,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { } EncodeContext encodeContext = new EncodeContext(); - String resourceName = myContext.getResourceDefinition(theResource).getName(); + String resourceName = myContext.getResourceType(theResource); encodeContext.pushPath(resourceName, true); doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter, encodeContext); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java index b4dcbd7e6e7..cdd8149997c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java @@ -20,8 +20,18 @@ package ca.uhn.fhir.parser; * #L% */ -import ca.uhn.fhir.context.*; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeChildDefinition.IMutator; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition; +import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; +import ca.uhn.fhir.context.RuntimePrimitiveDatatypeNarrativeDefinition; +import ca.uhn.fhir.context.RuntimePrimitiveDatatypeXhtmlHl7OrgDefinition; +import ca.uhn.fhir.context.RuntimeResourceBlockDefinition; +import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IIdentifiableElement; @@ -45,7 +55,22 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.tuple.Pair; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseBackboneElement; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseElement; +import org.hl7.fhir.instance.model.api.IBaseExtension; +import org.hl7.fhir.instance.model.api.IBaseHasExtensions; +import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions; +import org.hl7.fhir.instance.model.api.IBaseMetaType; +import org.hl7.fhir.instance.model.api.IBaseReference; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IBaseXhtml; +import org.hl7.fhir.instance.model.api.ICompositeType; +import org.hl7.fhir.instance.model.api.IDomainResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; @@ -1055,7 +1080,7 @@ class ParserState { } private void stitchBundleCrossReferences() { - final boolean bundle = "Bundle".equals(myContext.getResourceDefinition(myInstance).getName()); + final boolean bundle = "Bundle".equals(myContext.getResourceType(myInstance)); if (bundle) { FhirTerser t = myContext.newTerser(); @@ -1078,7 +1103,7 @@ class ParserState { for (IBaseResource next : myGlobalResources) { IIdType id = next.getIdElement(); if (id != null && !id.isEmpty()) { - String resName = myContext.getResourceDefinition(next).getName(); + String resName = myContext.getResourceType(next); IIdType idType = id.withResourceType(resName).toUnqualifiedVersionless(); idToResource.put(idType.getValueAsString(), next); } @@ -1172,7 +1197,7 @@ class ParserState { IResource nextResource = (IResource) getCurrentElement(); String version = ResourceMetadataKeyEnum.VERSION.get(nextResource); - String resourceName = myContext.getResourceDefinition(nextResource).getName(); + String resourceName = myContext.getResourceType(nextResource); String bundleIdPart = nextResource.getId().getIdPart(); if (isNotBlank(bundleIdPart)) { // if (isNotBlank(entryBaseUrl)) { @@ -1221,7 +1246,7 @@ class ParserState { if (getCurrentElement() instanceof IDomainResource) { IDomainResource elem = (IDomainResource) getCurrentElement(); - String resourceName = myContext.getResourceDefinition(elem).getName(); + String resourceName = myContext.getResourceType(elem); String versionId = elem.getMeta().getVersionId(); if (StringUtils.isBlank(elem.getIdElement().getIdPart())) { // Resource has no ID diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java index 551fc818583..ed2e9aecf3b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java @@ -288,7 +288,7 @@ public class RDFParser extends BaseParser { } case RESOURCE: { IBaseResource baseResource = (IBaseResource) element; - String resourceName = this.context.getResourceDefinition(baseResource).getName(); + String resourceName = this.context.getResourceType(baseResource); if (!super.shouldEncodeResource(resourceName)) { break; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java index a09ad1208be..f908606be66 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java @@ -295,7 +295,7 @@ public class XmlParser extends BaseParser { } case RESOURCE: { IBaseResource resource = (IBaseResource) theElement; - String resourceName = myContext.getResourceDefinition(resource).getName(); + String resourceName = myContext.getResourceType(resource); if (!super.shouldEncodeResource(resourceName)) { break; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java index 89cb4343942..16ca3957a97 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java @@ -10,11 +10,9 @@ import ca.uhn.fhir.util.DateUtils; import org.hl7.fhir.instance.model.api.IPrimitiveType; import java.util.ArrayList; -import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Objects; -import java.util.TimeZone; import static ca.uhn.fhir.rest.param.ParamPrefixEnum.EQUAL; import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN_OR_EQUALS; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java index d5a47c3909a..8debeba2b3a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java @@ -687,7 +687,7 @@ public class FhirTerser { IBaseResource nextTarget = nextValue.getResource(); nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless(); if (!nextTargetId.hasResourceType()) { - String resourceType = myContext.getResourceDefinition(nextTarget).getName(); + String resourceType = myContext.getResourceType(nextTarget); nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null); } nextRef = nextTargetId.getValue(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JsonUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/JsonUtil.java similarity index 97% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JsonUtil.java rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/util/JsonUtil.java index 506998c0192..d1816730538 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JsonUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/JsonUtil.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.util; +package ca.uhn.fhir.util; /*- * #%L - * HAPI FHIR JPA Server + * HAPI FHIR - Core Library * %% * Copyright (C) 2014 - 2020 University Health Network * %% diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/MetaUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/MetaUtil.java index 775c274e929..ca513f88fda 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/MetaUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/MetaUtil.java @@ -25,7 +25,12 @@ import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.rest.api.Constants; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseExtension; +import org.hl7.fhir.instance.model.api.IBaseHasExtensions; +import org.hl7.fhir.instance.model.api.IBaseMetaType; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java index 78d870f9376..6c3f72e2cae 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java @@ -345,4 +345,61 @@ public class ParametersUtil { partChildElem.getChildByName("resource").getMutator().addValue(part, theValue); } + public static List getNamedParameterPartAsString(FhirContext theCtx, IBaseParameters theParameters, String thePartName, String theParameterName) { + return extractNamedParameterPartsAsString(theCtx, theParameters, thePartName, theParameterName); + } + + // TODO KHS need to consolidate duplicated functionality that came in from different branches + private static List extractNamedParameterPartsAsString(FhirContext theCtx, IBaseParameters theParameters, String thePartName, String theParameterName) { + List parameterReps = getParameterReps(theCtx, theParameters); + + List retVal = new ArrayList<>(); + + for (IBase nextParameter : parameterReps) { + BaseRuntimeElementCompositeDefinition nextParameterDef = (BaseRuntimeElementCompositeDefinition) theCtx.getElementDefinition(nextParameter.getClass()); + Optional> nameValue = getNameValue(nextParameter, nextParameterDef); + if (!nameValue.isPresent() || !thePartName.equals(nameValue.get().getValueAsString())) { + continue; + } + + BaseRuntimeChildDefinition partChild = nextParameterDef.getChildByName("part"); + List partValues = partChild.getAccessor().getValues(nextParameter); + for (IBase partValue : partValues) { + BaseRuntimeElementCompositeDefinition partParameterDef = (BaseRuntimeElementCompositeDefinition) theCtx.getElementDefinition(partValue.getClass()); + Optional> partNameValue = getNameValue(partValue, partParameterDef); + if (!partNameValue.isPresent() || !theParameterName.equals(partNameValue.get().getValueAsString())) { + continue; + } + BaseRuntimeChildDefinition valueChild = partParameterDef.getChildByName("value[x]"); + List valueValues = valueChild.getAccessor().getValues(partValue); + valueValues + .stream() + .filter(t -> t instanceof IPrimitiveType) + .map(t -> ((IPrimitiveType) t)) + .map(t -> defaultIfBlank(t.getValueAsString(), null)) + .filter(t -> t != null) + .forEach(retVal::add); + + } + } + return retVal; + } + + private static List getParameterReps(FhirContext theCtx, IBaseParameters theParameters) { + Validate.notNull(theParameters, "theParameters must not be null"); + RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass()); + BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter"); + return parameterChild.getAccessor().getValues(theParameters); + } + + private static Optional> getNameValue(IBase nextParameter, BaseRuntimeElementCompositeDefinition theNextParameterDef) { + BaseRuntimeChildDefinition nameChild = theNextParameterDef.getChildByName("name"); + List nameValues = nameChild.getAccessor().getValues(nextParameter); + return nameValues + .stream() + .filter(t -> t instanceof IPrimitiveType) + .map(t -> ((IPrimitiveType) t)) + .findFirst(); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ResourceReferenceInfo.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ResourceReferenceInfo.java index aaf5b7f2afc..5de822e0e07 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ResourceReferenceInfo.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ResourceReferenceInfo.java @@ -45,7 +45,7 @@ public class ResourceReferenceInfo { public ResourceReferenceInfo(FhirContext theContext, IBaseResource theOwningResource, List thePathToElement, IBaseReference theElement) { myContext = theContext; - myOwningResource = theContext.getResourceDefinition(theOwningResource).getName(); + myOwningResource = theContext.getResourceType(theOwningResource); myResource = theElement; if (thePathToElement != null && !thePathToElement.isEmpty()) { diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/StringNormalizer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/StringNormalizer.java similarity index 92% rename from hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/StringNormalizer.java rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/util/StringNormalizer.java index 1ab703d0eaf..02b3e7fd4c8 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/StringNormalizer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/StringNormalizer.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.model.util; +package ca.uhn.fhir.util; /*- * #%L - * HAPI FHIR Model + * HAPI FHIR - Core Library * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -24,7 +24,7 @@ import java.io.CharArrayWriter; import java.text.Normalizer; public class StringNormalizer { - public static String normalizeString(String theString) { + public static String normalizeStringForSearchIndexing(String theString) { CharArrayWriter outBuffer = new CharArrayWriter(theString.length()); /* diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IAnyResource.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IAnyResource.java index a60eec40aa2..2ea80eb6d17 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IAnyResource.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IAnyResource.java @@ -23,6 +23,9 @@ import ca.uhn.fhir.rest.gclient.TokenClientParam; * #L% */ +/** + * An IBaseResource that has a FHIR version of DSTU3 or higher + */ public interface IAnyResource extends IBaseResource { /** diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/interceptor/model/RequestPartitionIdTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/interceptor/model/RequestPartitionIdTest.java index 4664c991f11..6045284ffe1 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/interceptor/model/RequestPartitionIdTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/interceptor/model/RequestPartitionIdTest.java @@ -4,7 +4,8 @@ import org.junit.Test; import java.time.LocalDate; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; public class RequestPartitionIdTest { diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml index 2bc9a198065..1029fa41c2a 100644 --- a/hapi-fhir-bom/pom.xml +++ b/hapi-fhir-bom/pom.xml @@ -36,6 +36,11 @@ hapi-fhir-server ${project.version} + + ${project.groupId} + hapi-fhir-server-empi + ${project.version} + ${project.groupId} hapi-fhir-validation @@ -96,6 +101,11 @@ hapi-fhir-jpaserver-model ${project.version} + + ${project.groupId} + hapi-fhir-jpaserver-empi + ${project.version} + ${project.groupId} hapi-fhir-jpaserver-searchparam diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java index 89a9db47432..37b8cf4c529 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java @@ -129,7 +129,7 @@ public class ExampleDataUploader extends BaseCommand { } ourLog.info("Found example {} - {} - {} chars", nextEntry.getName(), parsed.getClass().getSimpleName(), exampleString.length()); - if (ctx.getResourceDefinition(parsed).getName().equals("Bundle")) { + if (ctx.getResourceType(parsed).equals("Bundle")) { BaseRuntimeChildDefinition entryChildDef = ctx.getResourceDefinition(parsed).getChildByName("entry"); BaseRuntimeElementCompositeDefinition entryDef = (BaseRuntimeElementCompositeDefinition) entryChildDef.getChildByName("entry"); @@ -139,13 +139,13 @@ public class ExampleDataUploader extends BaseCommand { continue; } for (IBase nextResource : resources) { - if (!ctx.getResourceDefinition(parsed).getName().equals("Bundle") && ctx.getResourceDefinition(parsed).getName().equals("SearchParameter")) { + if (!ctx.getResourceType(parsed).equals("Bundle") && ctx.getResourceType(parsed).equals("SearchParameter")) { bundle.addEntry().setRequest(new EntryRequest().setMethod(HTTPVerbEnum.POST)).setResource((IResource) nextResource); } } } } else { - if (ctx.getResourceDefinition(parsed).getName().equals("SearchParameter")) { + if (ctx.getResourceType(parsed).equals("SearchParameter")) { continue; } bundle.addEntry().setRequest(new EntryRequest().setMethod(HTTPVerbEnum.POST)).setResource((IResource) parsed); @@ -205,7 +205,7 @@ public class ExampleDataUploader extends BaseCommand { continue; } - if (ctx.getResourceDefinition(parsed).getName().equals("Bundle")) { + if (ctx.getResourceType(parsed).equals("Bundle")) { BaseRuntimeChildDefinition entryChildDef = ctx.getResourceDefinition(parsed).getChildByName("entry"); BaseRuntimeElementCompositeDefinition entryDef = (BaseRuntimeElementCompositeDefinition) entryChildDef.getChildByName("entry"); @@ -227,7 +227,7 @@ public class ExampleDataUploader extends BaseCommand { } } } else { - if (ctx.getResourceDefinition(parsed).getName().equals("SearchParameter")) { + if (ctx.getResourceType(parsed).equals("SearchParameter")) { continue; } BundleEntryComponent entry = bundle.addEntry(); @@ -289,7 +289,7 @@ public class ExampleDataUploader extends BaseCommand { continue; } - if (ctx.getResourceDefinition(parsed).getName().equals("Bundle")) { + if (ctx.getResourceType(parsed).equals("Bundle")) { BaseRuntimeChildDefinition entryChildDef = ctx.getResourceDefinition(parsed).getChildByName("entry"); BaseRuntimeElementCompositeDefinition entryDef = (BaseRuntimeElementCompositeDefinition) entryChildDef.getChildByName("entry"); @@ -311,7 +311,7 @@ public class ExampleDataUploader extends BaseCommand { } } } else { - if (ctx.getResourceDefinition(parsed).getName().equals("SearchParameter")) { + if (ctx.getResourceType(parsed).equals("SearchParameter")) { continue; } org.hl7.fhir.r4.model.Bundle.BundleEntryComponent entry = bundle.addEntry(); @@ -663,7 +663,7 @@ public class ExampleDataUploader extends BaseCommand { for (Iterator iter = resources.iterator(); iter.hasNext(); ) { IBaseResource next = iter.next(); - String nextType = ctx.getResourceDefinition(next).getName(); + String nextType = ctx.getResourceType(next); if (nextType.endsWith("Definition")) { iter.remove(); } else if (nextType.contains("ValueSet")) { diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu2.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu2.java index 52b9df7659a..865f3e812fc 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu2.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu2.java @@ -36,7 +36,7 @@ public class LoadingValidationSupportDstu2 implements IValidationSupport { @Override public T fetchResource(Class theClass, String theUri) { - String resName = myCtx.getResourceDefinition(theClass).getName(); + String resName = myCtx.getResourceType(theClass); ourLog.info("Attempting to fetch {} at URL: {}", resName, theUri); myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java index 25847c89348..7d22c3e65aa 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java @@ -36,7 +36,7 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport { @Override public T fetchResource(Class theClass, String theUri) { - String resName = myCtx.getResourceDefinition(theClass).getName(); + String resName = myCtx.getResourceType(theClass); ourLog.info("Attempting to fetch {} at URL: {}", resName, theUri); myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java index 4416770676c..27e02441c57 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java @@ -34,7 +34,7 @@ public class LoadingValidationSupportR4 implements IValidationSupport { @Override public T fetchResource(Class theClass, String theUri) { - String resName = myCtx.getResourceDefinition(theClass).getName(); + String resName = myCtx.getResourceType(theClass); ourLog.info("Attempting to fetch {} at URL: {}", resName, theUri); myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HapiFlywayMigrateDatabaseCommandTest.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HapiFlywayMigrateDatabaseCommandTest.java index 79dfd067536..6a1c15fcab5 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HapiFlywayMigrateDatabaseCommandTest.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HapiFlywayMigrateDatabaseCommandTest.java @@ -26,7 +26,9 @@ import java.util.List; import java.util.Map; import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class HapiFlywayMigrateDatabaseCommandTest { @@ -37,7 +39,6 @@ public class HapiFlywayMigrateDatabaseCommandTest { System.setProperty("test", "true"); } - // TODO INTERMITTENT This just failed for me on CI with a BadSqlGrammarException @Test public void testMigrateFrom340() throws IOException, SQLException { diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapDstu3.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapDstu3.java index 3e86c205e80..f1700628342 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapDstu3.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapDstu3.java @@ -22,7 +22,11 @@ package ca.uhn.fhir.cli; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -31,7 +35,6 @@ import com.google.common.base.Charsets; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; import org.hl7.fhir.dstu3.model.ConceptMap; -import org.hl7.fhir.dstu3.model.IdType; import java.net.URI; import java.net.URISyntaxException; diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapR4.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapR4.java index 2ae3a4b8a0b..b47a8a7e947 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapR4.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HashMapResourceProviderConceptMapR4.java @@ -22,7 +22,11 @@ package ca.uhn.fhir.cli; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -31,7 +35,6 @@ import com.google.common.base.Charsets; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; import org.hl7.fhir.r4.model.ConceptMap; -import org.hl7.fhir.r4.model.IdType; import java.net.URI; import java.net.URISyntaxException; diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java index 7dc79278c2d..1fdd38b4c75 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.demo; * #L% */ -import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; 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 58e26b6b06d..41da2296758 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 @@ -23,10 +23,10 @@ package ca.uhn.fhir.jpa.demo; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; -import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.config.BaseConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.config.BaseConfig; import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; @@ -36,7 +36,6 @@ import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; @@ -45,6 +44,7 @@ import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; +import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; diff --git a/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulRequest.java b/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulRequest.java index 9f8a393e0e1..143b9c9b47d 100644 --- a/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulRequest.java +++ b/hapi-fhir-client-okhttp/src/main/java/ca/uhn/fhir/okhttp/client/OkHttpRestfulRequest.java @@ -1,5 +1,15 @@ package ca.uhn.fhir.okhttp.client; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.client.api.BaseHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; +import ca.uhn.fhir.util.StopWatch; +import okhttp3.Call; +import okhttp3.Call.Factory; +import okhttp3.Request; +import okhttp3.RequestBody; + import java.io.IOException; import java.util.Collections; import java.util.List; @@ -25,16 +35,6 @@ import java.util.Map; * #L% */ -import ca.uhn.fhir.rest.api.RequestTypeEnum; -import ca.uhn.fhir.rest.client.api.BaseHttpRequest; -import ca.uhn.fhir.rest.client.api.IHttpRequest; -import ca.uhn.fhir.rest.client.api.IHttpResponse; -import ca.uhn.fhir.util.StopWatch; -import okhttp3.Call; -import okhttp3.Call.Factory; -import okhttp3.Request; -import okhttp3.RequestBody; - /** * Adapter for building an OkHttp-specific request. * diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java index b197726b367..a3f3f248d3c 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java @@ -361,7 +361,7 @@ public class GenericClient extends BaseClient implements IGenericClient { } private String toResourceName(Class theType) { - return myContext.getResourceDefinition(theType).getName(); + return myContext.getResourceType(theType); } @Override @@ -787,7 +787,7 @@ public class GenericClient extends BaseClient implements IGenericClient { public IDeleteWithQuery resourceConditionalByType(Class theResourceType) { Validate.notNull(theResourceType, "theResourceType can not be null"); myConditional = true; - myResourceType = myContext.getResourceDefinition(theResourceType).getName(); + myResourceType = myContext.getResourceType(theResourceType); return this; } @@ -904,7 +904,7 @@ public class GenericClient extends BaseClient implements IGenericClient { String resourceName; String id; if (myType != null) { - resourceName = myContext.getResourceDefinition(myType).getName(); + resourceName = myContext.getResourceType(myType); id = null; } else if (myId != null) { resourceName = myId.getResourceType(); @@ -1282,7 +1282,7 @@ public class GenericClient extends BaseClient implements IGenericClient { String id; String version; if (myType != null) { - resourceName = myContext.getResourceDefinition(myType).getName(); + resourceName = myContext.getResourceType(myType); id = null; version = null; } else if (myId != null) { @@ -1559,7 +1559,7 @@ public class GenericClient extends BaseClient implements IGenericClient { @Override public IPatchWithQuery conditional(Class theClass) { Validate.notNull(theClass, "theClass must not be null"); - String resourceType = myContext.getResourceDefinition(theClass).getName(); + String resourceType = myContext.getResourceType(theClass); return conditional(resourceType); } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseOutcomeReturningMethodBindingWithResourceParam.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseOutcomeReturningMethodBindingWithResourceParam.java index 34bc1278b47..2ead2b72f12 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseOutcomeReturningMethodBindingWithResourceParam.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseOutcomeReturningMethodBindingWithResourceParam.java @@ -59,7 +59,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu throw new ConfigurationException("Unable to determine resource type for method: " + theMethod); } - myResourceName = theContext.getResourceDefinition(myResourceType).getName(); + myResourceName = theContext.getResourceType(myResourceType); if (resourceParameter == null) { throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " does not have a resource parameter annotated with @" + ResourceParam.class.getSimpleName()); diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseResourceReturningMethodBinding.java index 771a123c0d4..7b0a9128a65 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/BaseResourceReturningMethodBinding.java @@ -99,7 +99,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi // If we're returning an abstract type, that's ok } else { myResourceType = (Class) theReturnResourceType; - myResourceName = theContext.getResourceDefinition(myResourceType).getName(); + myResourceName = theContext.getResourceType(myResourceType); } } } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/DeleteMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/DeleteMethodBinding.java index 46391030628..7c8b2a3555a 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/DeleteMethodBinding.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/DeleteMethodBinding.java @@ -53,7 +53,7 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBindingWithRe @Override protected BaseHttpClientInvocation createClientInvocation(Object[] theArgs, IBaseResource theResource) { StringBuilder urlExtension = new StringBuilder(); - urlExtension.append(getContext().getResourceDefinition(theResource).getName()); + urlExtension.append(getContext().getResourceType(theResource)); return new HttpPostClientInvocation(getContext(), theResource, urlExtension.toString()); } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HistoryMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HistoryMethodBinding.java index 3d485820d30..a5f4074f4ad 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HistoryMethodBinding.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HistoryMethodBinding.java @@ -64,7 +64,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { } if (type != IBaseResource.class && type != IResource.class) { - myResourceName = theContext.getResourceDefinition(type).getName(); + myResourceName = theContext.getResourceType(type); } else { myResourceName = null; } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpSimpleGetClientInvocation.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpSimpleGetClientInvocation.java index 6dde247b5fc..adadf8be108 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpSimpleGetClientInvocation.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HttpSimpleGetClientInvocation.java @@ -20,9 +20,6 @@ package ca.uhn.fhir.rest.client.method; * #L% */ -import java.util.List; -import java.util.Map; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum; @@ -30,6 +27,9 @@ import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.api.UrlSourceEnum; import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation; +import java.util.List; +import java.util.Map; + public class HttpSimpleGetClientInvocation extends BaseHttpClientInvocation { private final String myUrl; diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/MethodUtil.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/MethodUtil.java index 5042bd0e810..064e2a070d5 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/MethodUtil.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/MethodUtil.java @@ -160,7 +160,7 @@ public class MethodUtil { public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IBaseResource theResource, String theResourceBody, Map> theMatchParams) { - String resourceType = theContext.getResourceDefinition(theResource).getName(); + String resourceType = theContext.getResourceType(theResource); StringBuilder b = createUrl(resourceType, theMatchParams); @@ -188,7 +188,7 @@ public class MethodUtil { public static HttpPutClientInvocation createUpdateInvocation(IBaseResource theResource, String theResourceBody, IIdType theId, FhirContext theContext) { - String resourceName = theContext.getResourceDefinition(theResource).getName(); + String resourceName = theContext.getResourceType(theResource); StringBuilder urlBuilder = new StringBuilder(); urlBuilder.append(resourceName); urlBuilder.append('/'); diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/OperationMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/OperationMethodBinding.java index 11bf910a1f2..737d5f509c8 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/OperationMethodBinding.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/OperationMethodBinding.java @@ -83,10 +83,10 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding { myName = theOperationName; if (theReturnTypeFromRp != null) { - setResourceName(theContext.getResourceDefinition(theReturnTypeFromRp).getName()); + setResourceName(theContext.getResourceType(theReturnTypeFromRp)); } else { if (Modifier.isAbstract(theOperationType.getModifiers()) == false) { - setResourceName(theContext.getResourceDefinition(theOperationType).getName()); + setResourceName(theContext.getResourceType(theOperationType)); } else { setResourceName(null); } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/PatchMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/PatchMethodBinding.java index d18cd1f946b..452297877d6 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/PatchMethodBinding.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/PatchMethodBinding.java @@ -83,7 +83,7 @@ public class PatchMethodBinding extends BaseOutcomeReturningMethodBindingWithRes @Override protected BaseHttpClientInvocation createClientInvocation(Object[] theArgs, IBaseResource theResource) { StringBuilder urlExtension = new StringBuilder(); - urlExtension.append(getContext().getResourceDefinition(theResource).getName()); + urlExtension.append(getContext().getResourceType(theResource)); return new HttpPostClientInvocation(getContext(), theResource, urlExtension.toString()); } diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/ValidateMethodBindingDstu2Plus.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/ValidateMethodBindingDstu2Plus.java index 24a5fea1d38..121ab6913e1 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/ValidateMethodBindingDstu2Plus.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/ValidateMethodBindingDstu2Plus.java @@ -29,7 +29,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.valueset.BundleTypeEnum; -import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.Validate; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; @@ -72,7 +71,7 @@ public class ValidateMethodBindingDstu2Plus extends OperationMethodBinding { IBaseParameters parameters = (IBaseParameters) theContext.getResourceDefinition("Parameters").newInstance(); ParametersUtil.addParameterToParameters(theContext, parameters, "resource", theResource); - String resourceName = theContext.getResourceDefinition(theResource).getName(); + String resourceName = theContext.getResourceType(theResource); String resourceId = theResource.getIdElement().getIdPart(); BaseHttpClientInvocation retVal = createOperationInvocation(theContext, resourceName, resourceId, null,Constants.EXTOP_VALIDATE, parameters, false); diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/changes.yaml new file mode 100644 index 00000000000..a4ea16a4b6e --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/changes.yaml @@ -0,0 +1,8 @@ +--- +- item: + issue: "1857" + type: "change" + title: "**Breaking Change**: + The method `FhirContext#getResourceNames()` has been renamed to `FhirContext#getResourceTypes()`. HAPI currently + goes back and forth between the two, but is consolidating on `Types`. + " diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties index bb548ffca44..dc5fe3ad700 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties @@ -48,6 +48,11 @@ page.server_jpa.performance=Performance page.server_jpa.upgrading=Upgrade Guide page.server_jpa.diff=Diff Operation +section.server_jpa_empi.title=JPA Server: EMPI +page.server_jpa_empi.empi=Enterprise Master Patient Index +page.server_jpa_empi.empi_operations=EMPI Operations +page.server_jpa_empi.empi_settings=Enabling EMPI in HAPI FHIR + section.server_jpa_partitioning.title=JPA Server: Partitioning and Multitenancy page.server_jpa_partitioning.partitioning=Partitioning and Multitenancy page.server_jpa_partitioning.partitioning_management_operations=Partitioning Management Operations diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/empi-links.svg b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/empi-links.svg new file mode 100644 index 00000000000..b6cd0f904b6 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/empi-links.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/diff.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/diff.md index c328be9c1fd..584a808a30e 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/diff.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/diff.md @@ -12,8 +12,8 @@ When the $diff operation is invoked at the instance level (meaning it is invoked ## Parameters -* `[[${T(ca.uhn.fhir.jpa.model.util.ProviderConstants).DIFF_FROM_VERSION_PARAMETER}]]=[versionId]`: (*optional*) If specified, compare using this version as the source. If not specified, the immediately previous version will be compared. -* `[[${T(ca.uhn.fhir.jpa.model.util.ProviderConstants).DIFF_INCLUDE_META_PARAMETER}]]=true`: (*optional*) If specified, changes to Resource.meta will be included in the diff. This element is omitted by default. +* `[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_FROM_VERSION_PARAMETER}]]=[versionId]`: (*optional*) If specified, compare using this version as the source. If not specified, the immediately previous version will be compared. +* `[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_INCLUDE_META_PARAMETER}]]=true`: (*optional*) If specified, changes to Resource.meta will be included in the diff. This element is omitted by default. To invoke: @@ -51,14 +51,14 @@ When the $diff operation is invoked at the instance level (meaning it is invoked ## Parameters -* `[[${T(ca.uhn.fhir.jpa.model.util.ProviderConstants).DIFF_FROM_PARAMETER}]]=[reference]`: Specifies the source of the comparison. The value must include a resource type and a resource ID, and can optionally include a version, e.g. `Patient/123` or `Patient/123/_history/2`. -* `[[${T(ca.uhn.fhir.jpa.model.util.ProviderConstants).DIFF_TO_PARAMETER}]]=[reference]`: Specifies the target of the comparison. The value must include a resource type and a resource ID, and can optionally include a version, e.g. `Patient/123` or `Patient/123/_history/2`. -* `[[${T(ca.uhn.fhir.jpa.model.util.ProviderConstants).DIFF_INCLUDE_META_PARAMETER}]]=true`: (*optional*) If specified, changes to Resource.meta will be included in the diff. This element is omitted by default. +* `[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_FROM_PARAMETER}]]=[reference]`: Specifies the source of the comparison. The value must include a resource type and a resource ID, and can optionally include a version, e.g. `Patient/123` or `Patient/123/_history/2`. +* `[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_TO_PARAMETER}]]=[reference]`: Specifies the target of the comparison. The value must include a resource type and a resource ID, and can optionally include a version, e.g. `Patient/123` or `Patient/123/_history/2`. +* `[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_INCLUDE_META_PARAMETER}]]=true`: (*optional*) If specified, changes to Resource.meta will be included in the diff. This element is omitted by default. To invoke: ```http -GET http://fhir.example.com/baseR4/$diff?[[${T(ca.uhn.fhir.jpa.model.util.ProviderConstants).DIFF_FROM_PARAMETER}]]=Patient/1&[[${T(ca.uhn.fhir.jpa.model.util.ProviderConstants).DIFF_TO_PARAMETER}]]=Patient/2 +GET http://fhir.example.com/baseR4/$diff?[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_FROM_PARAMETER}]]=Patient/1&[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_TO_PARAMETER}]]=Patient/2 ``` The server will produce a response resembling the following: diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi.md new file mode 100644 index 00000000000..205c5cee9fa --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi.md @@ -0,0 +1,158 @@ +# Enterprise Master Person Index (EMPI) + +HAPI FHIR 5.0.0 introduced preliminary support for **EMPI**. + +An EMPI allows for links to be created and maintained between different Patient and/or Practitioner resources. These links are used to indicate the fact that different Patient/Practitioner resources are known or believed to refer to the same actual (real world) person. + +These links may be created and updated using different combinations of automatic linking as well as manual linking. + +Note: The following sections describe linking between Patient and Person resources. The same information applies for linking between Practitioner and Person, but for readability it is not repeated. + +## Working Example + +The [JPA Server Starter](/hapi-fhir/docs/server_jpa/get_started.html) project contains a complete working example of the HAPI EMPI feature and documentation about how to enable and configure it. You may wish to browse its source to see how this works. + +## Person linking in FHIR + +Because HAPI EMPI is implemented on the HAPI JPA Server, it uses the FHIR model to represent roles and links. The following illustration shows an example of how these links work. + +EMPI links + +There are several resources that are used: + +* Patient - Represents the record of a person who receives healthcare services +* Person - Represents a master record with links to one or more Patient and/or Practitioner resources that belong to the same person + +# Automatic Linking + +With EMPI enabled, the basic default behavior of the EMPI is simply to create a new Person record for every Patient that is created such that there is a 1:1 relationship between them. Any relinking is then expected to be done manually (i.e. via the forthcoming empi operations). + +In a typical configuration it is often desirable to have links be created automatically using matching rules. For example, you might decide that if a Patient shares the same name, gender, and date of birth as another Patient, you have at least a little confidence that they are the same Person. + +This automatic linking is done via configurable matching rules that create a links between Patients and Persons. Based on the strength of the match configured in these rules, the link will be set to either POSSIBLE_MATCH or MATCHED. + +## Design Principles + +Below are some simplifying principles HAPI EMPI enforces to reduce complexity and ensure data integrity. + +1. When EMPI is enabled on a HAPI FHIR server, any Person resource in the repository that has the "hapi-empi" tag is considered read-only via the FHIR endpoint. These Person resources are managed exclusively by HAPI EMPI. Users can only directly change them via special empi operations. In most cases, users will indirectly change them by creating and updating Patient and Practitioner ("Patient") resources. For the rest of this document, assume "Person" refers to a "hapi-empi" tagged Person resource. + +1. Every Patient in the system has a MATCH link to at most one Person resource. + +1. Every Patient resource in the system has a MATCH link to a Person resource unless that Patient has the "no-empi" tag or it has POSSIBLE_MATCH links pending review. + +1. The HAPI EMPI rules define a single identifier system that holds the external enterprise id ("EID"). If a Patient has an external EID, then the Person it links to always has the same EID. If a patient has no EID when it arrives, the person created from this patient is given an internal EID. + +1. A Person can have both an internal EID(auto-created by HAPI), and an external EID (provided by an external system). + +1. Two different Person resources cannot have the same EID. + +1. Patient resources are only ever compared to Person resources via this EID. For all other matches, Patient resources are only ever compared to Patient resources and Practitioner resources are only ever compared to Practitioner resources. + +## Links + +1. HAPI EMPI manages empi-link records ("links") that link a Patient resource to a Person resource. When these are created/updated by matching rules, the links are marked as AUTO. When these links are changed manually, they are marked as MANUAL. + +1. Once a link has been manually assigned as NO_MATCH or MATCHED, the system will not change it. + +1. When a new Patient resource is created/updated then it is compared to all other Patient resources in the repository. The outcome of each of these comparisons is either NO_MATCH, POSSIBLE_MATCH or MATCHED. + +1. Whenever a MATCHED link is established between a Patient resource and a Person resource, that Patient is always added to that Person resource links. All MATCHED links have corresponding Person resource links and all Person resource links have corresponding MATCHED empi-link records. You can think of the fields of the empi-link records as extra meta-data associated with each Person.link.target. + +### Possible rule match outcomes: + +When a new Patient resource is compared with all other resources of that type in the repository, there are four possible cases: + +* CASE 1: No MATCHED and no POSSIBLE_MATCHED outcomes -> a new Person resource is created and linked to that Patient as MATCHED. All fields are copied from the Patient to the Person. If the incoming resource has an EID, it is copied to the Person. Otherwise a new UUID is created and used as the internal EID. + +* CASE 2: All of the MATCHED Patient resources are already linked to the same Person -> a new Link is created between the new Patient and that Person and is set to MATCHED. + +* CASE 3: The MATCHED Patient resources link to more than one Person -> Mark all links as POSSIBLE_MATCHED. All other Person resources are marked as POSSIBLE_DUPLICATE of this first Person. These duplicates are manually reviewed later and either merged or marked as NO_MATCH and the system will no longer consider them as a POSSIBLE_DUPLICATE going forward. POSSIBLE_DUPLICATE is the only link type that can have a Person as both the source and target of the link. + +* CASE 4: Only POSSIBLE_MATCH outcomes -> In this case, empi-link records are created with POSSIBLE_MATCH outcome and await manual assignment to either NO_MATCH or MATCHED. Person resources are not changed. + +# Rules + +HAPI EMPI rules are managed via a single json document. This document contains a version. empi-links derived from these rules are marked with this version. The following configuration is stored in the rules: + +* **resourceSearchParams**: These define fields which must have at least one exact match before two resources are considered for matching. This is like a list of "pre-searches" that find potential candidates for matches, to avoid the expensive operation of running a match score calculation on all resources in the system. E.g. you may only wish to consider matching two Patients if they either share at least one identifier in common or have the same birthday. +```json +[ { + "resourceType" : "Patient", + "searchParam" : "birthdate" +}, { + "resourceType" : "Patient", + "searchParam" : "identifier" +} ] +``` + +* **filterSearchParams** When searching for match candidates, only resources that match this filter are considered. E.g. you may wish to only search for Patients for which active=true. +```json +[ { + "resourceType" : "Patient", + "searchParam" : "active", + "fixedValue" : "true" +} ] +``` + +* **matchFields** Once the match candidates have been found, they are then each assigned a match vector that marks which fields match. The match vector is determined by a list of matchFields. Each matchField defines a name, distance metric, a success threshold, a resource type, and resource path to check. For example: +```json +{ + "name" : "given-name-cosine", + "resourceType" : "Patient", + "resourcePath" : "name.given", + "metric" : "COSINE", + "matchThreshold" : 0.8 +} +``` + +Note that in all the above json, valid options for `resourceType` are `Patient`, `Practitioner`, and `All`. Use `All` if the criteria is identical across both resource types, and you would like to apply the pre-search to both practitioners and patients. + +The following metrics are currently supported: +* JARO_WINKLER +* COSINE +* JACCARD +* NORMALIZED_LEVENSCHTEIN +* SORENSEN_DICE +* STANDARD_NAME_ANY_ORDER +* EXACT_NAME_ANY_ORDER +* STANDARD_NAME_FIRST_AND_LAST +* EXACT_NAME_FIRST_AND_LAST + +See [java-string-similarity](https://github.com/tdebatty/java-string-similarity) for a description of the first five metrics. For the last four, STANDARd means ignore case and accents whereas EXACT must match casing and accents exactly. Name any order matches first and last names irrespective of order, whereas FIRST_AND_LAST metrics require the name match to be in order. + +* **matchResultMap** A map which converts combinations of successful matchFields into an EMPI Match Result score for overall matching of a given pair of resources. + +```json +"matchResultMap" : { + "given-name-cosine" : "POSSIBLE_MATCH", + "given-name-jaro, last-name-jaro" : "MATCH" +} +``` + +* **eidSystem**: The external EID system that the HAPI EMPI system should expect to see on incoming Patient resources. Must be a valid URI. + +# Enterprise Identifiers + +An Enterprise Identifier(EID) is a unique identifier that can be attached to Patients or Practitioners. Each implementation is expected to use exactly one EID system for incoming resources, +defined in the mentioned `empi-rules.json` file. If a Patient or Practitioner with a valid EID is added to the system, that EID will be copied over to the Person that was matched. In the case that +the incoming Patient or Practitioner had no EID assigned, an internal EID will be created for it. There are thus two classes of EID. Internal EIDs, created by HAPI-EMPI, and External EIDs, provided +by the install. + +There are many edge cases for determining what will happen in merge and update scenarios, which will be provided in future documentation. + + +# HAPI EMPI Technical Details + +When EMPI is enabled, the HAPI FHIR JPA Server does the following things on startup: + +1. HAPI EMPI stores the extra link details in a table called `MPI_LINK`. +1. Each record in an `MPI_LINK` table corresponds to a `link.target` entry on a Person resource. HAPI EMPI uses the following convention for the Person.link.assurance level: + 1. Level 1: not used + 1. Level 2: POSSIBLE_MATCH + 1. Level 3: AUTO MATCHED + 1. Level 4: MANUAL MATCHED +1. It enables the MESSAGE subscription type and starts up the internal subscription engine. +1. It creates two MESSAGE subscriptions, called 'empi-patient' and 'empi-practitioner' that match all incoming Patient and Practitioner resources and send them to an internal queue called "empi". The JPA Server listens to this queue and links incoming resources to Persons. +1. It registers the `Patient/$match` operation. See [$match](https://www.hl7.org/fhir/operation-patient-match.html) for a description of this operation. +1. It registers a new dao interceptor that restricts access to EMPI managed Person records. diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_operations.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_operations.md new file mode 100644 index 00000000000..50bd076fb8e --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_operations.md @@ -0,0 +1,362 @@ +# EMPI Operations + +Several operations exist that can be used to manage EMPI links. These operations are supplied by a [plain provider](/docs/server_plain/resource_providers.html#plain-providers) called [EmpiProvider](/hapi-fhir/apidocs/hapi-fhir-server-empi/ca/uhn/fhir/empi/provider/EmpiProviderR4.html). + +## Query links + +Ue the `$empi-query-links` operation to view empi links. The results returned are based on the parameters provided. All parameters are optional. This operation takes the following parameters: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeCardinalityDescription
personIdString0..1 + The id of the Person resource. +
targetIdString0..1 + The id of the Patient or Practitioner resource. +
matchResultString0..1 + MATCH, POSSIBLE_MATCH or NO_MATCH. +
linkSourceString0..1 + AUTO, MANUAL. +
+ +### Example + +Use an HTTP GET like `http://example.com/$empi-query-link?matchResult=POSSIBLE_MATCH` or an HTTP POST to the following URL to invoke this operation: + +```url +http://example.com/$empi-query-link +``` + +The following request body could be used to find all POSSIBLE_MATCH links in the system: + +```json +{ + "resourceType": "Parameters", + "parameter": [ { + "name": "matchResult", + "valueString": "POSSIBLE_MATCH" + } ] +} +``` + +This operation returns a `Parameters` resource that looks like the following: + +```json + + + + + + + + + + + + + + + + + + + + + +``` + +## Querying links via the Person resource + +Alternatively, you can query Empi links by querying Person resources directly. Empi represents links in Person resources using the following mapping: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EMPI matchResultEMPI linkSourcePerson link.assurance
NO_MATCHMANUALNo link present
POSSIBLE_MATCHAUTOlevel2
MATCHAUTOlevel3
MATCHMANUALlevel4
+ +For example, you can use the following HTTP GET to find all Person resources that have POSSIBLE_MATCH links: + +``` +http://example.com/Person?assurance=level2 +``` + +## Query Duplicate Persons + +Use the `$empi-duplicate-persons` operation to request a list of duplicate persons. This operation takes no parameters + +### Example + +Use an HTTP GET to the following URL to invoke this operation: + +```url +http://example.com/$empi-duplicate-persons +``` + +This operation returns `Parameters` similar to `$empi-query-links`: + + +```json + + + + + + + + + + + + + +``` + +## Update Link + +Use the `$empi-update-link` operation to change the `matchResult` update of an empi link. This operation takes the following parameters: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeCardinalityDescription
personIdString1..1 + The id of the Person resource. +
targetIdString1..1 + The id of the Patient or Practitioner resource. +
matchResultString1..1 + Must be either MATCH or NO_MATCH. +
+ +Empi links updated in this way will automatically have their `linkSource` set to `MANUAL`. + +### Example + +Use an HTTP POST to the following URL to invoke this operation: + +```url +http://example.com/$empi-update-link +``` + +The following request body could be used: + +```json +{ + "resourceType": "Parameters", + "parameter": [ { + "name": "personId", + "valueString": "Person/123" + }, { + "name": "targetId", + "valueString": "Patient/456" + }, { + "name": "matchResult", + "valueString": "MATCH" + } ] +} +``` + +The operation returns the updated `Person` resource. Note that this is the only way to modify EMPI-managed `Person` resources. + +## Merge Persons + +The `$empi-merge-persons` operation can be used to merge one Person resource with another. When doing this, you will need to decide which resource to delete and which one to keep. Data from the personToKeep will be given precedence over data in the personToDelete. This operation takes the following parameters: + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeCardinalityDescription
personIdToDeleteString1..1 + The id of the Person resource to merge data from. This resource will be deleted after the merge. +
personIdToKeepString1..1 + The id of the Person to merge data into. +
+ +### Example + +Use an HTTP POST to the following URL to invoke this operation: + +```url +http://example.com/$empi-merge-persons +``` + +The following request body could be used: + +```json +{ + "resourceType": "Parameters", + "parameter": [ { + "name": "personIdToDelete", + "valueString": "Person/123" + }, { + "name": "personIdToKeep", + "valueString": "Patient/128" + } ] +} +``` + +This operation returns the merged Person resource. + +# Querying The EMPI + +When EMPI is enabled, the [$match operation](http://hl7.org/fhir/patient-operation-match.html) will be enabled on the JPA Server. + +This operation allows a Patient resource to be submitted to the endpoint, and the system will attempt to find and return any Patient resources that match it according to the matching rules. + +For example, the following request may be submitted: + +```http +POST /Patient/$match +Content-Type: application/fhir+json; charset=UTF-8 + +{ + "resourceType":"Parameters", + "parameter": [ + { + "name":"resource", + "resource": { + "resourceType":"Patient", + "name": [ + { "family":"foo" } + ] + } + } + ] +} +``` + +This might result in a response such as the following: + +```json +{ + "resourceType": "Bundle", + "id": "0e712adc-6979-4875-bbe9-70b883a955b8", + "meta": { + "lastUpdated": "2019-06-06T22:46:43.809+03:30" + }, + "type": "searchset", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "3", + "meta": { + "versionId": "1", + "lastUpdated": "2019-06-06T22:46:43.339+03:30" + }, + "name": [ + { + "family": "foo", + "given": [ + "bar" + ] + } + ], + "birthDate": "2000-01-01" + } + } + ] +} +``` diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_settings.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_settings.md new file mode 100644 index 00000000000..c76792831ae --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_settings.md @@ -0,0 +1,11 @@ +# Enabling EMPI in HAPI FHIR + +Follow these steps to enable EMPI on the server: + +The [EmpiSettings](/hapi-fhir/apidocs/hapi-fhir-server-empi/ca/uhn/fhir/empi/rules/config/EmpiSettings.html) bean contains configuration settings related to EMPI within the server. To enable Empi, the [setEnabled(boolean)](/hapi-fhir/apidocs/hapi-fhir-server-empi/ca/uhn/fhir/empi/rules/config/EmpiSettings.html#setEnabled(boolean)) property should be enabled. + +The following settings are enabled by default: + +* **Prevent EID Updates** ([JavaDoc](/hapi-fhir/apidocs/hapi-fhir-server-empi/ca/uhn/fhir/empi/rules/config/EmpiSettings.html#setPreventEidUpdates(boolean))): If this is enabled, then once an EID is set on a resource, it cannot be changed. If disabled, patients may have their EID updated. + +* **Prevent multiple EIDs**: ([JavaDoc](/hapi-fhir/apidocs/hapi-fhir-server-empi/ca/uhn/fhir/empi/rules/config/EmpiSettings.html#setPreventMultipleEids(boolean))): If this is enabled, then a resource cannot have more than one EID, and incoming resources that break this rule will be rejected. diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index 22f91a814b8..1c0564d16b9 100644 --- a/hapi-fhir-jacoco/pom.xml +++ b/hapi-fhir-jacoco/pom.xml @@ -31,6 +31,11 @@ hapi-fhir-server ${project.version}
+ + ca.uhn.hapi.fhir + hapi-fhir-server-empi + ${project.version} + ca.uhn.hapi.fhir hapi-fhir-client @@ -101,6 +106,11 @@ hapi-fhir-jpaserver-model ${project.version} + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-empi + ${project.version} + ca.uhn.hapi.fhir hapi-fhir-jpaserver-api diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/DaoRegistry.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/DaoRegistry.java index d0c26ad60f3..6d406c109a1 100644 --- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/DaoRegistry.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/DaoRegistry.java @@ -23,8 +23,6 @@ package ca.uhn.fhir.jpa.api.dao; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.api.IDaoRegistry; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.apache.commons.lang3.Validate; @@ -35,7 +33,14 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import javax.annotation.Nullable; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; public class DaoRegistry implements ApplicationContextAware, IDaoRegistry { @@ -76,7 +81,6 @@ public class DaoRegistry implements ApplicationContextAware, IDaoRegistry { public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException { myAppCtx = theApplicationContext; } - public IFhirSystemDao getSystemDao() { IFhirSystemDao retVal = mySystemDao; if (retVal == null) { @@ -118,7 +122,7 @@ public class DaoRegistry implements ApplicationContextAware, IDaoRegistry { @Nullable public IFhirResourceDao getResourceDaoOrNull(Class theResourceType) { - String resourceName = myContext.getResourceDefinition(theResourceType).getName(); + String resourceName = myContext.getResourceType(theResourceType); try { return (IFhirResourceDao) getResourceDao(resourceName); } catch (InvalidRequestException e) { @@ -184,10 +188,10 @@ public class DaoRegistry implements ApplicationContextAware, IDaoRegistry { List supportedResourceNames = myResourceNameToResourceDao .keySet() .stream() - .map(t -> myContext.getResourceDefinition(t).getName()) + .map(t -> myContext.getResourceType(t)) .sorted() .collect(Collectors.toList()); - throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + myContext.getResourceDefinition(theClass).getName() + " - Can handle: " + supportedResourceNames); + throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + myContext.getResourceType(theClass) + " - Can handle: " + supportedResourceNames); } return retVal; } diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoEncounter.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoEncounter.java index b94396b1495..65aea252217 100644 --- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoEncounter.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoEncounter.java @@ -1,5 +1,12 @@ package ca.uhn.fhir.jpa.api.dao; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.DateRangeParam; +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 javax.servlet.http.HttpServletRequest; /* @@ -21,11 +28,6 @@ import javax.servlet.http.HttpServletRequest; * limitations under the License. * #L% */ -import org.hl7.fhir.instance.model.api.*; - -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.DateRangeParam; public interface IFhirResourceDaoEncounter extends IFhirResourceDao { diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoValueSet.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoValueSet.java index fa30e3576de..acd038d9ef0 100644 --- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoValueSet.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoValueSet.java @@ -19,9 +19,11 @@ package ca.uhn.fhir.jpa.api.dao; * limitations under the License. * #L% */ -import org.hl7.fhir.instance.model.api.*; import ca.uhn.fhir.rest.api.server.RequestDetails; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; public interface IFhirResourceDaoValueSet extends IFhirResourceDao { diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyCurrentlyReindexing.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyCurrentlyReindexing.java index c5778090d84..759bca8cd4b 100644 --- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyCurrentlyReindexing.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyCurrentlyReindexing.java @@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.api.dao; * #L% */ -import ca.uhn.fhir.jpa.api.dao.IDao; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum.ResourceMetadataKeySupportingAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource; diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteMethodOutcome.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteMethodOutcome.java index e1ab49e8263..bbc4f5b3983 100644 --- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteMethodOutcome.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DeleteMethodOutcome.java @@ -20,11 +20,11 @@ package ca.uhn.fhir.jpa.api.model; * #L% */ -import java.util.List; - import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.rest.api.MethodOutcome; +import java.util.List; + /** * This class is a replacement for {@link DaoMethodOutcome} for delete operations, * as they can perform their operation over multiple resources diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 03911637adc..329ce50f301 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 @@ -14,29 +15,29 @@ HAPI FHIR JPA Server - + + org.apache.commons + commons-lang3 + 3.5 + + --> org.codehaus.woodstox woodstox-core-asl - + net.sf.saxon @@ -74,6 +75,11 @@ hapi-fhir-server ${project.version} + + ca.uhn.hapi.fhir + hapi-fhir-server-empi + ${project.version} + ca.uhn.hapi.fhir hapi-fhir-jpaserver-subscription @@ -210,9 +216,9 @@ - io.dogote - json-patch - + io.dogote + json-patch + com.github.dnault xml-patch @@ -229,9 +235,9 @@ For some reason JavaDoc crashed during site generation unless we have this dependency --> - javax.interceptor - javax.interceptor-api - provided + javax.interceptor + javax.interceptor-api + provided - + javax.servlet javax.servlet-api @@ -380,8 +386,8 @@ javax.activation-api - - + + @@ -581,8 +587,7 @@ org.jetbrains annotations - - + @@ -640,14 +645,14 @@ ${jaxb_runtime_version} - - - + + + - - - + + + @@ -690,7 +695,8 @@ dstu2 ca.uhn.fhir.jpa.config ca.uhn.fhir.jpa.rp.dstu2 - hapi-fhir-server-resourceproviders-dstu2.xml + hapi-fhir-server-resourceproviders-dstu2.xml + @@ -706,7 +712,8 @@ dstu3 ca.uhn.fhir.jpa.config ca.uhn.fhir.jpa.rp.dstu3 - hapi-fhir-server-resourceproviders-dstu3.xml + hapi-fhir-server-resourceproviders-dstu3.xml + @@ -721,7 +728,8 @@ r4 ca.uhn.fhir.jpa.config ca.uhn.fhir.jpa.rp.r4 - hapi-fhir-server-resourceproviders-r4.xml + hapi-fhir-server-resourceproviders-r4.xml + @@ -736,7 +744,8 @@ r5 ca.uhn.fhir.jpa.config ca.uhn.fhir.jpa.rp.r5 - hapi-fhir-server-resourceproviders-r5.xml + hapi-fhir-server-resourceproviders-r5.xml + @@ -868,6 +877,6 @@ - +
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryAccessProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryAccessProvider.java index be047d6f3df..73a1ec7a0f7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryAccessProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryAccessProvider.java @@ -223,7 +223,7 @@ public class BinaryAccessProvider { @Nonnull private IBinaryTarget findAttachmentForRequest(IBaseResource theResource, String thePath, ServletRequestDetails theRequestDetails) { Optional type = myCtx.newFluentPath().evaluateFirst(theResource, thePath, IBase.class); - String resType = this.myCtx.getResourceDefinition(theResource).getName(); + String resType = this.myCtx.getResourceType(theResource); if (!type.isPresent()) { String msg = this.myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownPath", resType, thePath); throw new InvalidRequestException(msg); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryStorageInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryStorageInterceptor.java index 2def3baee36..e3c0029a303 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryStorageInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/BinaryStorageInterceptor.java @@ -181,7 +181,7 @@ public class BinaryStorageInterceptor { IIdType resourceId = theResource.getIdElement(); if (!resourceId.hasResourceType() && resourceId.hasIdPart()) { - String resourceType = myCtx.getResourceDefinition(theResource).getName(); + String resourceType = myCtx.getResourceType(theResource); resourceId = new IdType(resourceType + "/" + resourceId.getIdPart()); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProvider.java index d8ac59fb5f6..abccc5547b0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProvider.java @@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.bulk; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.util.JsonUtil; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.api.Constants; @@ -31,6 +30,7 @@ import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.ArrayUtil; +import ca.uhn.fhir.util.JsonUtil; import ca.uhn.fhir.util.OperationOutcomeUtil; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.StringUtils; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java index fdc931cfe23..84a51212cec 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/BulkDataExportSvcImpl.java @@ -378,7 +378,7 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc { // This is probably not a useful default, but having the default be "download the whole // server" seems like a risky default too. We'll deal with that by having the default involve // only returning a small time span - resourceTypes = myContext.getResourceNames(); + resourceTypes = myContext.getResourceTypes(); if (since == null) { since = DateUtils.addDays(new Date(), -1); } 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 307243b1942..259a04db35c 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 @@ -103,7 +103,8 @@ import java.util.Date; @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*\\.test\\..*"), @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Test.*"), @ComponentScan.Filter(type = FilterType.REGEX, pattern = "ca.uhn.fhir.jpa.subscription.*"), - @ComponentScan.Filter(type = FilterType.REGEX, pattern = "ca.uhn.fhir.jpa.searchparam.*") + @ComponentScan.Filter(type = FilterType.REGEX, pattern = "ca.uhn.fhir.jpa.searchparam.*"), + @ComponentScan.Filter(type = FilterType.REGEX, pattern = "ca.uhn.fhir.jpa.empi.*") }) @Import({ SearchParamConfig.class @@ -122,6 +123,8 @@ public abstract class BaseConfig { @Autowired protected Environment myEnv; + @Autowired + private DaoRegistry myDaoRegistry; @Bean("myDaoRegistry") public DaoRegistry daoRegistry() { @@ -244,7 +247,7 @@ public abstract class BaseConfig { * Subclasses may override */ protected boolean isSupported(String theResourceType) { - return daoRegistry().getResourceDaoOrNull(theResourceType) != null; + return myDaoRegistry.getResourceDaoOrNull(theResourceType) != null; } @Bean 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 8e85e7e02ab..3dd5bb69a27 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 @@ -7,7 +7,6 @@ import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.JpaPersistedResourceValidationSupport; -import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu2; import ca.uhn.fhir.jpa.term.TermReadSvcDstu2; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; 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 13e19195641..bd6b9dc0a84 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 @@ -9,7 +9,6 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.dstu3.TransactionProcessorVersionAdapterDstu3; import ca.uhn.fhir.jpa.provider.GraphQLProvider; -import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3; import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; import ca.uhn.fhir.jpa.term.TermReadSvcDstu3; import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcDstu3; 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 bf53a64685f..e3fff88ee0f 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 @@ -9,7 +9,6 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4; import ca.uhn.fhir.jpa.provider.GraphQLProvider; -import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4; import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; import ca.uhn.fhir.jpa.term.TermReadSvcR4; import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcR4; @@ -51,6 +50,8 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @EnableTransactionManagement public class BaseR4Config extends BaseConfigDstu3Plus { + public static FhirContext ourFhirContext = FhirContext.forR4(); + @Override public FhirContext fhirContext() { return fhirContextR4(); @@ -65,7 +66,7 @@ public class BaseR4Config extends BaseConfigDstu3Plus { @Bean @Primary public FhirContext fhirContextR4() { - FhirContext retVal = FhirContext.forR4(); + FhirContext retVal = ourFhirContext; // Don't strip versions in some places ParserOptions parserOptions = retVal.getParserOptions(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java index 78b6327e92d..615272a4e9c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/BaseR5Config.java @@ -9,7 +9,6 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.r5.TransactionProcessorVersionAdapterR5; import ca.uhn.fhir.jpa.provider.GraphQLProvider; -import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR5; import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; import ca.uhn.fhir.jpa.term.TermReadSvcR5; import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcR5; 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 9554f0fb7d3..fbd4abdbbea 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 @@ -964,11 +964,11 @@ public abstract class BaseHapiFhirDao extends BaseStora } public String toResourceName(Class theResourceType) { - return myContext.getResourceDefinition(theResourceType).getName(); + return myContext.getResourceType(theResourceType); } String toResourceName(IBaseResource theResource) { - return myContext.getResourceDefinition(theResource).getName(); + return myContext.getResourceType(theResource); } protected ResourceTable updateEntityForDelete(RequestDetails theRequest, TransactionDetails theTransactionDetails, ResourceTable entity) { @@ -997,7 +997,7 @@ public abstract class BaseHapiFhirDao extends BaseStora validateResourceForStorage((T) theResource, entity); } } - String resourceType = myContext.getResourceDefinition(theResource).getName(); + String resourceType = myContext.getResourceType(theResource); if (isNotBlank(entity.getResourceType()) && !entity.getResourceType().equals(resourceType)) { throw new UnprocessableEntityException( "Existing resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + entity.getResourceType() + "] - Cannot update with [" + resourceType + "]"); @@ -1346,7 +1346,7 @@ public abstract class BaseHapiFhirDao extends BaseStora allowAny = true; break; } - validTypes.add(getContext().getResourceDefinition(nextValidType).getName()); + validTypes.add(getContext().getResourceType(nextValidType)); } if (allowAny) { @@ -1417,7 +1417,7 @@ public abstract class BaseHapiFhirDao extends BaseStora throw new UnprocessableEntityException("Resource contains the 'subsetted' tag, and must not be stored as it may contain a subset of available data"); } - String resName = getContext().getResourceDefinition(theResource).getName(); + String resName = getContext().getResourceType(theResource); validateChildReferences(theResource, resName); validateMetaCount(totalMetaCount); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java index 079af73adcd..5b8594e0f4e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java @@ -85,7 +85,7 @@ public abstract class BaseStorageDao { * @param theResource The resource that is about to be stored */ protected void preProcessResourceForStorage(IBaseResource theResource) { - String type = getContext().getResourceDefinition(theResource).getName(); + String type = getContext().getResourceType(theResource); if (getResourceName() != null && !getResourceName().equals(type)) { throw new InvalidRequestException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "incorrectResourceType", type, getResourceName())); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java index 5dbb24a33f8..b1ba2102bb0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java @@ -649,7 +649,7 @@ public abstract class BaseTransactionProcessor { } String verb = myVersionAdapter.getEntryRequestVerb(myContext, nextReqEntry); - String resourceType = res != null ? myContext.getResourceDefinition(res).getName() : null; + String resourceType = res != null ? myContext.getResourceType(res) : null; Integer order = theOriginalRequestOrder.get(nextReqEntry); IBase nextRespEntry = (IBase) myVersionAdapter.getEntries(theResponse).get(order); @@ -997,7 +997,7 @@ public abstract class BaseTransactionProcessor { private String toResourceName(Class theResourceType) { - return myContext.getResourceDefinition(theResourceType).getName(); + return myContext.getResourceType(theResourceType); } public void setContext(FhirContext theContext) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EmpiLinkDaoSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EmpiLinkDaoSvc.java new file mode 100644 index 00000000000..ca57593dd61 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EmpiLinkDaoSvc.java @@ -0,0 +1,195 @@ +package ca.uhn.fhir.jpa.dao; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.empi.model.EmpiTransactionContext; +import ca.uhn.fhir.jpa.dao.data.IEmpiLinkDao; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Optional; + +@Service +public class EmpiLinkDaoSvc { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + private IEmpiLinkDao myEmpiLinkDao; + @Autowired + private IdHelperService myIdHelperService; + + public EmpiLink createOrUpdateLinkEntity(IBaseResource thePerson, IBaseResource theTarget, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, @Nullable EmpiTransactionContext theEmpiTransactionContext) { + Long personPid = myIdHelperService.getPidOrNull(thePerson); + Long resourcePid = myIdHelperService.getPidOrNull(theTarget); + + EmpiLink empiLink = getOrCreateEmpiLinkByPersonPidAndTargetPid(personPid, resourcePid); + empiLink.setLinkSource(theLinkSource); + empiLink.setMatchResult(theMatchResult); + + String message = String.format("Creating EmpiLink from %s to %s -> %s", thePerson.getIdElement().toUnqualifiedVersionless(), theTarget.getIdElement().toUnqualifiedVersionless(), theMatchResult); + theEmpiTransactionContext.addTransactionLogMessage(message); + ourLog.debug(message); + save(empiLink); + return empiLink; + } + + + @Nonnull + public EmpiLink getOrCreateEmpiLinkByPersonPidAndTargetPid(Long thePersonPid, Long theResourcePid) { + Optional oExisting = getLinkByPersonPidAndTargetPid(thePersonPid, theResourcePid); + if (oExisting.isPresent()) { + return oExisting.get(); + } else { + EmpiLink empiLink = new EmpiLink(); + empiLink.setPersonPid(thePersonPid); + empiLink.setTargetPid(theResourcePid); + return empiLink; + } + } + + public Optional getLinkByPersonPidAndTargetPid(Long thePersonPid, Long theTargetPid) { + + if (theTargetPid == null || thePersonPid == null) { + return Optional.empty(); + } + EmpiLink link = new EmpiLink(); + link.setTargetPid(theTargetPid); + link.setPersonPid(thePersonPid); + Example example = Example.of(link); + return myEmpiLinkDao.findOne(example); + } + + public List getEmpiLinksByTargetPidAndMatchResult(Long theTargetPid, EmpiMatchResultEnum theMatchResult) { + EmpiLink exampleLink = new EmpiLink(); + exampleLink.setTargetPid(theTargetPid); + exampleLink.setMatchResult(theMatchResult); + Example example = Example.of(exampleLink); + return myEmpiLinkDao.findAll(example); + } + + public Optional getMatchedLinkForTargetPid(Long theTargetPid) { + EmpiLink exampleLink = new EmpiLink(); + exampleLink.setTargetPid(theTargetPid); + exampleLink.setMatchResult(EmpiMatchResultEnum.MATCH); + Example example = Example.of(exampleLink); + return myEmpiLinkDao.findOne(example); + } + + public Optional getMatchedLinkForTarget(IBaseResource theTarget) { + Long pid = myIdHelperService.getPidOrNull(theTarget); + if (pid == null) { + return Optional.empty(); + } + + EmpiLink exampleLink = new EmpiLink(); + exampleLink.setTargetPid(pid); + exampleLink.setMatchResult(EmpiMatchResultEnum.MATCH); + Example example = Example.of(exampleLink); + return myEmpiLinkDao.findOne(example); + } + + public Optional getEmpiLinksByPersonPidTargetPidAndMatchResult(Long thePersonPid, Long theTargetPid, EmpiMatchResultEnum theMatchResult) { + EmpiLink exampleLink = new EmpiLink(); + exampleLink.setPersonPid(thePersonPid); + exampleLink.setTargetPid(theTargetPid); + exampleLink.setMatchResult(theMatchResult); + Example example = Example.of(exampleLink); + return myEmpiLinkDao.findOne(example); + } + + /** + * Get all {@link EmpiLink} which have {@link EmpiMatchResultEnum#POSSIBLE_DUPLICATE} as their match result. + * + * @return A list of EmpiLinks that hold potential duplicate persons. + */ + public List getPossibleDuplicates() { + EmpiLink exampleLink = new EmpiLink(); + exampleLink.setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE); + Example example = Example.of(exampleLink); + return myEmpiLinkDao.findAll(example); + } + + public Optional findEmpiLinkByTarget(IBaseResource theTargetResource) { + @Nullable Long pid = myIdHelperService.getPidOrNull(theTargetResource); + if (pid == null) { + return Optional.empty(); + } + EmpiLink empiLink = new EmpiLink().setTargetPid(pid); + Example example = Example.of(empiLink); + return myEmpiLinkDao.findOne(example); + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void deleteLink(EmpiLink theEmpiLink) { + myEmpiLinkDao.delete(theEmpiLink); + } + + /** + * Delete all EmpiLink records with any reference to this resource. (Used by Expunge.) + * @param theResource + * @return the number of records deleted + */ + public int deleteWithAnyReferenceTo(IBaseResource theResource) { + Long pid = myIdHelperService.getPidOrThrowException(theResource.getIdElement(), null); + int removed = myEmpiLinkDao.deleteWithAnyReferenceToPid(pid); + if (removed > 0) { + ourLog.info("Removed {} EMPI links with references to {}", removed, theResource.getIdElement().toVersionless()); + } + return removed; + } + + public List findEmpiLinksByPersonId(IBaseResource thePersonResource) { + @Nullable Long pid = myIdHelperService.getPidOrNull(thePersonResource); + if (pid == null) { + return Collections.emptyList(); + } + EmpiLink empiLink = new EmpiLink().setPersonPid(pid); + Example example = Example.of(empiLink); + return myEmpiLinkDao.findAll(example); + } + + public EmpiLink save(EmpiLink theEmpiLink) { + if (theEmpiLink.getCreated() == null) { + theEmpiLink.setCreated(new Date()); + } + theEmpiLink.setUpdated(new Date()); + return myEmpiLinkDao.save(theEmpiLink); + } + + public List findEmpiLinkByExample(Example theExampleLink) { + return myEmpiLinkDao.findAll(theExampleLink); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java index 0e1981ef0df..38313fc1c86 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java @@ -81,7 +81,18 @@ import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import javax.persistence.TypedQuery; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -373,7 +384,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionEntryHasInvalidVerb", nextReqEntry.getRequest().getMethod())); } - String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null; + String resourceType = res != null ? getContext().getResourceType(res) : null; Entry nextRespEntry = theResponse.getEntry().get(theOriginalRequestOrder.get(nextReqEntry)); switch (verb) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupport.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupport.java index bd8a11fee29..4af6519f71a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupport.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupport.java @@ -105,7 +105,7 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport localReference = true; } - String resourceName = myFhirContext.getResourceDefinition(theClass).getName(); + String resourceName = myFhirContext.getResourceType(theClass); IBundleProvider search; if ("ValueSet".equals(resourceName)) { if (localReference) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IEmpiLinkDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IEmpiLinkDao.java new file mode 100644 index 00000000000..75f231af2c3 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IEmpiLinkDao.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.jpa.dao.data; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 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.entity.EmpiLink; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface IEmpiLinkDao extends JpaRepository { + @Modifying + @Query("DELETE FROM EmpiLink f WHERE myPersonPid = :pid OR myTargetPid = :pid") + int deleteWithAnyReferenceToPid(@Param("pid") Long thePid); +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java index 8d48ec550a2..bae1abf40e2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeEverythingService.java @@ -23,8 +23,40 @@ package ca.uhn.fhir.jpa.dao.expunge; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.entity.*; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.entity.PartitionEntity; +import ca.uhn.fhir.jpa.entity.SearchInclude; +import ca.uhn.fhir.jpa.entity.SearchResult; +import ca.uhn.fhir.jpa.entity.SubscriptionTable; +import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.entity.TermConceptDesignation; +import ca.uhn.fhir.jpa.entity.TermConceptMap; +import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; +import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; +import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; +import ca.uhn.fhir.jpa.entity.TermConceptProperty; +import ca.uhn.fhir.jpa.entity.TermValueSet; +import ca.uhn.fhir.jpa.entity.TermValueSetConcept; +import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation; +import ca.uhn.fhir.jpa.model.entity.ForcedId; +import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity; +import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTag; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; +import ca.uhn.fhir.jpa.model.entity.ResourceLink; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.entity.ResourceTag; +import ca.uhn.fhir.jpa.model.entity.SearchParamPresent; +import ca.uhn.fhir.jpa.model.entity.TagDefinition; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; @@ -37,6 +69,7 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.support.TransactionTemplate; +import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @@ -64,7 +97,7 @@ public class ExpungeEverythingService { myTxTemplate = new TransactionTemplate(myPlatformTransactionManager); } - void expungeEverything(RequestDetails theRequest) { + public void expungeEverything(@Nullable RequestDetails theRequest) { final AtomicInteger counter = new AtomicInteger(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeService.java index 0eae943926a..cae11db09c5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeService.java @@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.dao.expunge; * #L% */ -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -35,8 +34,6 @@ import org.springframework.stereotype.Service; public abstract class ExpungeService { private static final Logger ourLog = LoggerFactory.getLogger(ExpungeService.class); - @Autowired - private DaoConfig myConfig; @Autowired private ExpungeEverythingService myExpungeEverythingService; @Autowired diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java index 25fc81202f6..8acfddbf04b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java @@ -27,9 +27,9 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.model.RequestPartitionId; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.cross.IResourceLookup; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java index 0631c1fbd46..218a27662f8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java @@ -28,11 +28,12 @@ import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.model.cross.IResourceLookup; import ca.uhn.fhir.jpa.model.cross.ResourceLookup; -import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ForcedId; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.util.QueryChunker; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import com.github.benmanes.caffeine.cache.Cache; @@ -42,12 +43,17 @@ import com.google.common.collect.MultimapBuilder; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.checkerframework.checker.nullness.qual.NonNull; +import org.hl7.fhir.instance.model.api.IAnyResource; +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.dao.IncorrectResultSizeDataAccessException; import org.springframework.stereotype.Service; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.Collection; @@ -60,6 +66,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -84,12 +91,14 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; */ @Service public class IdHelperService { + private static final Logger ourLog = LoggerFactory.getLogger(IdHelperService.class); + private static final String RESOURCE_PID = "RESOURCE_PID"; @Autowired protected IForcedIdDao myForcedIdDao; @Autowired protected IResourceTableDao myResourceTableDao; - @Autowired(required = true) + @Autowired private DaoConfig myDaoConfig; @Autowired private IInterceptorBroadcaster myInterceptorBroadcaster; @@ -107,7 +116,6 @@ public class IdHelperService { myForcedIdCache = newCache(); } - public void delete(ForcedId forcedId) { myForcedIdDao.deleteByPid(forcedId.getId()); } @@ -460,4 +468,48 @@ public class IdHelperService { public static boolean isValidPid(String theIdPart) { return StringUtils.isNumeric(theIdPart); } + + @Nullable + public Long getPidOrNull(IBaseResource theResource) { + IAnyResource anyResource = (IAnyResource) theResource; + Long retVal = (Long) anyResource.getUserData(RESOURCE_PID); + if (retVal == null) { + IIdType id = theResource.getIdElement(); + try { + retVal = this.resolveResourcePersistentIds(null, id.getResourceType(), id.getIdPart()).getIdAsLong(); + } catch (ResourceNotFoundException e) { + return null; + } + } + return retVal; + } + + @Nonnull + public Long getPidOrThrowException(IIdType theId) { + return getPidOrThrowException(theId, null); + } + + @Nonnull + public Long getPidOrThrowException(IAnyResource theResource) { + return (Long) theResource.getUserData(RESOURCE_PID); + } + + @Nonnull + public Long getPidOrThrowException(IIdType theId, RequestDetails theRequestDetails) { + List ids = Collections.singletonList(theId); + List resourcePersistentIds = this.resolveResourcePersistentIdsWithCache(RequestPartitionId.allPartitions(), ids); + return resourcePersistentIds.get(0).getIdAsLong(); + } + + public Map getPidToIdMap(Collection theIds, RequestDetails theRequestDetails) { + return theIds.stream().collect(Collectors.toMap(t->getPidOrThrowException(t), Function.identity())); + } + + public IIdType resourceIdFromPidOrThrowException(Long thePid) { + Optional optionalResource = myResourceTableDao.findById(thePid); + if (!optionalResource.isPresent()) { + throw new ResourceNotFoundException("Requested resource not found"); + } + return optionalResource.get().getIdDt().toVersionless(); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java index 2d97ade264b..10d47bef3e2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java @@ -24,13 +24,13 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; -import ca.uhn.fhir.jpa.model.util.StringNormalizer; import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; +import ca.uhn.fhir.util.StringNormalizer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -131,7 +131,7 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB } if (myDontUseHashesForSearch) { - String likeExpression = StringNormalizer.normalizeString(rawSearchTerm); + String likeExpression = StringNormalizer.normalizeStringForSearchIndexing(rawSearchTerm); if (myDaoConfig.isAllowContainsSearches()) { if (theParameter instanceof StringParam) { if (((StringParam) theParameter).isContains()) { @@ -161,7 +161,7 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB return theBuilder.equal(theFrom.get("myHashExact").as(Long.class), hash); } else { // Normalized Match - String normalizedString = StringNormalizer.normalizeString(rawSearchTerm); + String normalizedString = StringNormalizer.normalizeStringForSearchIndexing(rawSearchTerm); String likeExpression; if ((theParameter instanceof StringParam) && (((((StringParam) theParameter).isContains()) && diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java index 6ec78c4a2dd..a2dcd707009 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/DeleteConflictService.java @@ -24,17 +24,17 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.model.DeleteConflict; import ca.uhn.fhir.jpa.api.model.DeleteConflictList; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.api.model.DeleteConflict; -import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.OperationOutcomeUtil; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiLink.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiLink.java new file mode 100644 index 00000000000..41df01a2b6f --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiLink.java @@ -0,0 +1,205 @@ +package ca.uhn.fhir.jpa.entity; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.hibernate.annotations.OptimisticLock; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.UniqueConstraint; +import java.util.Date; + +@Entity +@Table(name = "MPI_LINK", uniqueConstraints = { + @UniqueConstraint(name = "IDX_EMPI_PERSON_TGT", columnNames = {"PERSON_PID", "TARGET_PID"}), +}) +public class EmpiLink { + private static final int MATCH_RESULT_LENGTH = 16; + private static final int LINK_SOURCE_LENGTH = 16; + + @SequenceGenerator(name = "SEQ_EMPI_LINK_ID", sequenceName = "SEQ_EMPI_LINK_ID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_EMPI_LINK_ID") + @Id + @Column(name = "PID") + private Long myId; + + @ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {}) + @JoinColumn(name = "PERSON_PID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_EMPI_LINK_PERSON"), insertable=false, updatable=false, nullable=false) + private ResourceTable myPerson; + + @Column(name = "PERSON_PID", nullable=false) + private Long myPersonPid; + + @ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {}) + @JoinColumn(name = "TARGET_PID", referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_EMPI_LINK_TARGET"), insertable=false, updatable=false, nullable=false) + private ResourceTable myTarget; + + @Column(name = "TARGET_PID", updatable=false, nullable=false) + private Long myTargetPid; + + @Column(name = "MATCH_RESULT", nullable = false) + @Enumerated(EnumType.ORDINAL) + private EmpiMatchResultEnum myMatchResult; + + @Column(name = "LINK_SOURCE", nullable = false) + @Enumerated(EnumType.ORDINAL) + private EmpiLinkSourceEnum myLinkSource; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "CREATED", nullable = false) + private Date myCreated; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "UPDATED", nullable = false) + private Date myUpdated; + + public Long getId() { + return myId; + } + + public EmpiLink setId(Long theId) { + myId = theId; + return this; + } + + public ResourceTable getPerson() { + return myPerson; + } + + public EmpiLink setPerson(ResourceTable thePerson) { + myPerson = thePerson; + myPersonPid = thePerson.getId(); + return this; + } + + public Long getPersonPid() { + return myPersonPid; + } + + public EmpiLink setPersonPid(Long thePersonPid) { + myPersonPid = thePersonPid; + return this; + } + + public ResourceTable getTarget() { + return myTarget; + } + + public EmpiLink setTarget(ResourceTable theTarget) { + myTarget = theTarget; + myTargetPid = theTarget.getId(); + return this; + } + + public Long getTargetPid() { + return myTargetPid; + } + + public EmpiLink setTargetPid(Long theTargetPid) { + myTargetPid = theTargetPid; + return this; + } + + public EmpiMatchResultEnum getMatchResult() { + return myMatchResult; + } + + public EmpiLink setMatchResult(EmpiMatchResultEnum theMatchResult) { + myMatchResult = theMatchResult; + return this; + } + + public boolean isNoMatch() { + return myMatchResult == EmpiMatchResultEnum.NO_MATCH; + } + + public boolean isMatch() { + return myMatchResult == EmpiMatchResultEnum.MATCH; + } + + public boolean isPossibleMatch() { + return myMatchResult == EmpiMatchResultEnum.POSSIBLE_MATCH; + } + + public EmpiLinkSourceEnum getLinkSource() { + return myLinkSource; + } + + public EmpiLink setLinkSource(EmpiLinkSourceEnum theLinkSource) { + myLinkSource = theLinkSource; + return this; + } + + public boolean isAuto() { + return myLinkSource == EmpiLinkSourceEnum.AUTO; + } + + public boolean isManual() { + return myLinkSource == EmpiLinkSourceEnum.MANUAL; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("myId", myId) + .append("myPersonPid", myPersonPid) + .append("myTargetPid", myTargetPid) + .append("myMatchResult", myMatchResult) + .append("myLinkSource", myLinkSource) + .toString(); + } + + public Date getCreated() { + return myCreated; + } + + public EmpiLink setCreated(Date theCreated) { + myCreated = theCreated; + return this; + } + + public Date getUpdated() { + return myUpdated; + } + + public EmpiLink setUpdated(Date theUpdated) { + myUpdated = theUpdated; + return this; + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceSearchView.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceSearchView.java index ceab0a6d8f8..733fb2868fd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceSearchView.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceSearchView.java @@ -32,7 +32,14 @@ import ca.uhn.fhir.rest.api.Constants; import org.hibernate.annotations.Immutable; import org.hibernate.annotations.Subselect; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; import java.io.Serializable; import java.util.Date; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/PartitionManagementProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/PartitionManagementProvider.java index 38e66b167ee..339d21614d0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/PartitionManagementProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/PartitionManagementProvider.java @@ -22,10 +22,10 @@ package ca.uhn.fhir.jpa.partition; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.entity.PartitionEntity; -import ca.uhn.fhir.jpa.model.util.ProviderConstants; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; import ca.uhn.fhir.util.ParametersUtil; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IPrimitiveType; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/RequestPartitionHelperSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/RequestPartitionHelperSvc.java index b102b3d64bf..ba331d911c8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/RequestPartitionHelperSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/partition/RequestPartitionHelperSvc.java @@ -130,7 +130,7 @@ public class RequestPartitionHelperSvc implements IRequestPartitionHelperSvc { .addIfMatchesType(ServletRequestDetails.class, theRequest); requestPartitionId = (RequestPartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE, params); - String resourceName = myFhirContext.getResourceDefinition(theResource).getName(); + String resourceName = myFhirContext.getResourceType(theResource); validatePartition(requestPartitionId, resourceName, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE); return normalizeAndNotifyHooks(requestPartitionId, theRequest); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java index 97fe1b54780..198c353ad01 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java @@ -2,9 +2,9 @@ package ca.uhn.fhir.jpa.provider; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; +import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/DiffProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/DiffProvider.java index 0a38fbc28c5..d4e55935397 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/DiffProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/DiffProvider.java @@ -23,7 +23,6 @@ package ca.uhn.fhir.jpa.provider; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.model.util.ProviderConstants; import ca.uhn.fhir.jpa.patch.FhirPatch; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; @@ -31,6 +30,7 @@ import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; import com.google.common.base.Objects; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -84,7 +84,6 @@ public class DiffProvider { FhirPatch fhirPatch = newPatch(theIncludeMeta); IBaseParameters diff = fhirPatch.diff(sourceResource, targetResource); return diff; - } @Operation(name = ProviderConstants.DIFF_OPERATION_NAME, idempotent = true) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java index bd35c45392a..b6b868457a9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaConformanceProviderDstu2.java @@ -23,8 +23,8 @@ package ca.uhn.fhir.jpa.provider; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/SubscriptionTriggeringProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/SubscriptionTriggeringProvider.java index 8aba30e107e..e31cc65e2da 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/SubscriptionTriggeringProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/SubscriptionTriggeringProvider.java @@ -22,13 +22,13 @@ package ca.uhn.fhir.jpa.provider; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.model.util.ProviderConstants; import ca.uhn.fhir.jpa.subscription.triggering.ISubscriptionTriggeringSvc; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; import org.hl7.fhir.instance.model.api.IBaseParameters; 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/provider/dstu3/JpaConformanceProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java index 1183ff5570a..c4e08a78eb4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaConformanceProviderDstu3.java @@ -22,8 +22,8 @@ package ca.uhn.fhir.jpa.provider.dstu3; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.RequestDetails; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java index b4a7f0bbc41..07851487069 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaConformanceProviderR4.java @@ -22,8 +22,8 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.RequestDetails; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaConformanceProviderR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaConformanceProviderR5.java index 83265e4957c..8693c7114f6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaConformanceProviderR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaConformanceProviderR5.java @@ -22,8 +22,8 @@ package ca.uhn.fhir.jpa.provider.r5; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.RequestDetails; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sched/BaseSchedulerServiceImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sched/BaseSchedulerServiceImpl.java index 49a05a83849..b2ae9e4c02a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sched/BaseSchedulerServiceImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/sched/BaseSchedulerServiceImpl.java @@ -110,7 +110,7 @@ public abstract class BaseSchedulerServiceImpl implements ISchedulerService, Sma } private IHapiScheduler createScheduler(boolean theClustered) throws SchedulerException { - if (!isLocalSchedulingEnabled() || isSchedulingDisabledForUnitTests()) { + if (isSchedulingDisabled()) { ourLog.info("Scheduling is disabled on this server"); return new HapiNullScheduler(); } @@ -126,6 +126,10 @@ public abstract class BaseSchedulerServiceImpl implements ISchedulerService, Sma return retval; } + private boolean isSchedulingDisabled() { + return !isLocalSchedulingEnabled() || isSchedulingDisabledForUnitTests(); + } + protected abstract IHapiScheduler getLocalHapiScheduler(); protected abstract IHapiScheduler getClusteredScheduler(); @@ -193,6 +197,9 @@ public abstract class BaseSchedulerServiceImpl implements ISchedulerService, Sma } private void scheduleJob(String theInstanceName, IHapiScheduler theScheduler, long theIntervalMillis, ScheduledJobDefinition theJobDefinition) { + if (isSchedulingDisabled()) { + return; + } ourLog.info("Scheduling {} job {} with interval {}", theInstanceName, theJobDefinition.getId(), StopWatch.formatMillis(theIntervalMillis)); if (theJobDefinition.getGroup() == null) { theJobDefinition.setGroup(myDefaultGroup); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java index 86e3298f467..d0777bed301 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java @@ -21,7 +21,6 @@ package ca.uhn.fhir.jpa.search; */ import ca.uhn.fhir.jpa.api.dao.DaoRegistry; -import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index ddf94398c53..f3255d40de6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -33,7 +33,6 @@ import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.dao.IResultIterator; import ca.uhn.fhir.jpa.dao.ISearchBuilder; import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; -import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.SearchInclude; import ca.uhn.fhir.jpa.entity.SearchTypeEnum; @@ -41,6 +40,7 @@ import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; +import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java index e0031ccba0b..ca2391c5ff9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java @@ -22,10 +22,10 @@ package ca.uhn.fhir.jpa.search.reindex; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/CacheWarmingSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/CacheWarmingSvcImpl.java index 17e945440e5..3f08e889059 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/CacheWarmingSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/warm/CacheWarmingSvcImpl.java @@ -23,9 +23,9 @@ package ca.uhn.fhir.jpa.search.warm; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.model.WarmCacheEntry; import ca.uhn.fhir.jpa.model.sched.HapiJob; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java index 84a47ae9fff..61141cbb839 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java @@ -24,18 +24,44 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.model.TranslationQuery; import ca.uhn.fhir.jpa.api.model.TranslationRequest; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; -import ca.uhn.fhir.jpa.dao.data.*; -import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; +import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptDesignationDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptMapDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementTargetDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptPropertyDao; +import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDao; +import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDesignationDao; +import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptViewDao; +import ca.uhn.fhir.jpa.dao.data.ITermValueSetDao; +import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.entity.TermConceptDesignation; +import ca.uhn.fhir.jpa.entity.TermConceptMap; +import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; +import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; +import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; +import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; -import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; +import ca.uhn.fhir.jpa.entity.TermConceptProperty; +import ca.uhn.fhir.jpa.entity.TermConceptPropertyFieldBridge; +import ca.uhn.fhir.jpa.entity.TermConceptPropertyTypeEnum; +import ca.uhn.fhir.jpa.entity.TermValueSet; +import ca.uhn.fhir.jpa.entity.TermValueSetConcept; +import ca.uhn.fhir.jpa.entity.TermValueSetConceptView; +import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.sched.HapiJob; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; @@ -47,6 +73,7 @@ import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; import ca.uhn.fhir.jpa.util.ScrollableResultsIterator; import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; @@ -81,7 +108,16 @@ import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome; import org.hl7.fhir.utilities.validation.ValidationOptions; import org.quartz.JobExecutionContext; @@ -110,7 +146,19 @@ import javax.persistence.criteria.Join; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.validation.constraints.NotNull; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java index cefaf47e85c..a0b3bdb3ed4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java @@ -23,8 +23,8 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.interceptor.model.RequestPartitionId; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.IHapiJpaRepository; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; @@ -74,7 +74,17 @@ import javax.annotation.Nonnull; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java index 43973d6a046..7b4fdb35225 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java @@ -4,12 +4,12 @@ import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.IValueSetConceptAccumulator; -import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.util.VersionIndependentConcept; import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseDatatype; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu2.java index 7b74ffb7c17..e48a289f6b8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu2.java @@ -48,14 +48,14 @@ public class SubscriptionsRequireManualActivationInterceptorDstu2 extends Server @Override public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) { - if (myDao.getContext().getResourceDefinition(theResource).getName().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { + if (myDao.getContext().getResourceType(theResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { verifyStatusOk(RestOperationTypeEnum.CREATE, null, theResource); } } @Override public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) { - if (myDao.getContext().getResourceDefinition(theNewResource).getName().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { + if (myDao.getContext().getResourceType(theNewResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { verifyStatusOk(RestOperationTypeEnum.UPDATE, theOldResource, theNewResource); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu3.java index 23b74d674d6..2f54b4cafff 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu3.java @@ -48,14 +48,14 @@ public class SubscriptionsRequireManualActivationInterceptorDstu3 extends Server @Override public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) { - if (myDao.getContext().getResourceDefinition(theResource).getName().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { + if (myDao.getContext().getResourceType(theResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { verifyStatusOk(RestOperationTypeEnum.CREATE, null, theResource); } } @Override public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) { - if (myDao.getContext().getResourceDefinition(theNewResource).getName().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { + if (myDao.getContext().getResourceType(theNewResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { verifyStatusOk(RestOperationTypeEnum.UPDATE, theOldResource, theNewResource); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorR4.java index 5983b3d787f..a7990614b7d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorR4.java @@ -48,14 +48,14 @@ public class SubscriptionsRequireManualActivationInterceptorR4 extends ServerOpe @Override public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) { - if (myDao.getContext().getResourceDefinition(theResource).getName().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { + if (myDao.getContext().getResourceType(theResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { verifyStatusOk(RestOperationTypeEnum.CREATE, null, theResource); } } @Override public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) { - if (myDao.getContext().getResourceDefinition(theNewResource).getName().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { + if (myDao.getContext().getResourceType(theNewResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) { verifyStatusOk(RestOperationTypeEnum.UPDATE, theOldResource, theNewResource); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/jsonpatch/JsonPatchUtils.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/jsonpatch/JsonPatchUtils.java index 516f7eb617a..97915e63008 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/jsonpatch/JsonPatchUtils.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/jsonpatch/JsonPatchUtils.java @@ -69,7 +69,7 @@ public class JsonPatchUtils { retVal = fhirJsonParser.parseResource(clazz, postPatchedContent); } catch (DataFormatException e) { String resourceId = theResourceToUpdate.getIdElement().toUnqualifiedVersionless().getValue(); - String resourceType = theCtx.getResourceDefinition(theResourceToUpdate).getName(); + String resourceType = theCtx.getResourceType(theResourceToUpdate); resourceId = defaultString(resourceId, resourceType); String msg = theCtx.getLocalizer().getMessage(JsonPatchUtils.class, "failedToApplyPatch", resourceId, e.getMessage()); throw new InvalidRequestException(msg); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaResourceLoader.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaResourceLoader.java new file mode 100644 index 00000000000..6b95ed99575 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/validation/JpaResourceLoader.java @@ -0,0 +1,40 @@ +package ca.uhn.fhir.jpa.validation; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2020 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.api.dao.DaoRegistry; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.validation.IResourceLoader; +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.stereotype.Service; + +@Service +public class JpaResourceLoader implements IResourceLoader { + @Autowired + DaoRegistry myDaoRegistry; + + @Override + public T load(Class theType, IIdType theId) throws ResourceNotFoundException { + return myDaoRegistry.getResourceDao(theType).read(theId); + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProviderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProviderTest.java index 99b50b40616..0d269f0e5e3 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProviderTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/bulk/BulkDataExportProviderTest.java @@ -2,16 +2,15 @@ package ca.uhn.fhir.jpa.bulk; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.util.JsonUtil; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.client.apache.ResourceEntity; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.test.utilities.JettyUtil; +import ca.uhn.fhir.util.JsonUtil; import ca.uhn.fhir.util.UrlUtil; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; -import org.apache.http.HttpRequestInterceptor; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; @@ -41,11 +40,16 @@ import java.util.Date; import java.util.Set; import java.util.concurrent.TimeUnit; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class BulkDataExportProviderTest { 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 d8f536c782c..d0185534982 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 @@ -6,8 +6,8 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; -import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; import ca.uhn.fhir.jpa.subscription.match.deliver.resthook.SubscriptionDeliveringRestHookSubscriber; +import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java index e502e72fce3..cca267e277e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java @@ -6,34 +6,34 @@ import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.executor.InterceptorService; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.dao.index.IdHelperService; -import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc; -import ca.uhn.fhir.test.BaseTest; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc; import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener; -import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.test.BaseTest; import ca.uhn.fhir.test.utilities.LoggingRule; import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor; import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.VersionIndependentConcept; import org.apache.commons.io.IOUtils; import org.hibernate.HibernateException; import org.hibernate.Session; @@ -44,7 +44,11 @@ import org.hl7.fhir.dstu3.model.Resource; 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.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -61,7 +65,11 @@ import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; 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 2cf7dea56fc..d9a7983f4e1 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 @@ -1,15 +1,16 @@ package ca.uhn.fhir.jpa.dao.dstu2; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.config.TestDstu2Config; import ca.uhn.fhir.jpa.dao.BaseJpaTest; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao; @@ -20,19 +21,45 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.api.svc.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.match.registry.SubscriptionLoader; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt; -import ca.uhn.fhir.model.dstu2.resource.*; +import ca.uhn.fhir.model.dstu2.resource.Appointment; +import ca.uhn.fhir.model.dstu2.resource.Binary; +import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.resource.Communication; +import ca.uhn.fhir.model.dstu2.resource.ConceptMap; +import ca.uhn.fhir.model.dstu2.resource.Conformance; +import ca.uhn.fhir.model.dstu2.resource.Device; +import ca.uhn.fhir.model.dstu2.resource.DiagnosticOrder; +import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport; +import ca.uhn.fhir.model.dstu2.resource.Encounter; +import ca.uhn.fhir.model.dstu2.resource.Group; +import ca.uhn.fhir.model.dstu2.resource.Immunization; +import ca.uhn.fhir.model.dstu2.resource.Location; +import ca.uhn.fhir.model.dstu2.resource.Media; +import ca.uhn.fhir.model.dstu2.resource.Medication; +import ca.uhn.fhir.model.dstu2.resource.MedicationAdministration; +import ca.uhn.fhir.model.dstu2.resource.MedicationOrder; +import ca.uhn.fhir.model.dstu2.resource.Observation; +import ca.uhn.fhir.model.dstu2.resource.Organization; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.dstu2.resource.Practitioner; +import ca.uhn.fhir.model.dstu2.resource.Questionnaire; +import ca.uhn.fhir.model.dstu2.resource.QuestionnaireResponse; +import ca.uhn.fhir.model.dstu2.resource.SearchParameter; +import ca.uhn.fhir.model.dstu2.resource.StructureDefinition; +import ca.uhn.fhir.model.dstu2.resource.Subscription; +import ca.uhn.fhir.model.dstu2.resource.Substance; +import ca.uhn.fhir.model.dstu2.resource.ValueSet; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.hibernate.search.jpa.FullTextEntityManager; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java index 40e0a205e08..2c6fd0baf92 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java @@ -1,27 +1,74 @@ package ca.uhn.fhir.jpa.dao.dstu2; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; -import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3Test; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.model.api.*; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.Tag; +import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.base.composite.BaseCodingDt; -import ca.uhn.fhir.model.dstu2.composite.*; -import ca.uhn.fhir.model.dstu2.resource.*; -import ca.uhn.fhir.model.dstu2.valueset.*; -import ca.uhn.fhir.model.primitive.*; +import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; +import ca.uhn.fhir.model.dstu2.composite.CodingDt; +import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; +import ca.uhn.fhir.model.dstu2.composite.MetaDt; +import ca.uhn.fhir.model.dstu2.composite.PeriodDt; +import ca.uhn.fhir.model.dstu2.composite.QuantityDt; +import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; +import ca.uhn.fhir.model.dstu2.resource.BaseResource; +import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.resource.ConceptMap; +import ca.uhn.fhir.model.dstu2.resource.Device; +import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport; +import ca.uhn.fhir.model.dstu2.resource.Encounter; +import ca.uhn.fhir.model.dstu2.resource.Observation; +import ca.uhn.fhir.model.dstu2.resource.Organization; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.dstu2.resource.Questionnaire; +import ca.uhn.fhir.model.dstu2.resource.StructureDefinition; +import ca.uhn.fhir.model.dstu2.resource.ValueSet; +import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum; +import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum; +import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; +import ca.uhn.fhir.model.dstu2.valueset.QuantityComparatorEnum; +import ca.uhn.fhir.model.primitive.CodeDt; +import ca.uhn.fhir.model.primitive.DateDt; +import ca.uhn.fhir.model.primitive.DateTimeDt; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; -import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.SortOrderEnum; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.*; -import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.ParamPrefixEnum; +import ca.uhn.fhir.rest.param.QuantityParam; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenOrListParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomStringUtils; @@ -29,17 +76,39 @@ import org.hamcrest.Matchers; import org.hamcrest.core.StringContains; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.defaultString; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; @SuppressWarnings("unchecked") public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java index 5c84fc6f97e..d9e4878ed14 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoValueSetDstu2Test.java @@ -1,22 +1,6 @@ package ca.uhn.fhir.jpa.dao.dstu2; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.stringContainsInOrder; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; - import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; -import org.springframework.transaction.annotation.Transactional; - import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.resource.ValueSet; @@ -25,6 +9,21 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.util.TestUtil; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java index ba4ca88a4b9..6b77b901256 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.dstu3; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoConceptMap; @@ -10,12 +11,22 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.config.TestDstu3Config; import ca.uhn.fhir.jpa.dao.BaseJpaTest; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; -import ca.uhn.fhir.jpa.dao.data.*; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao; +import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; +import ca.uhn.fhir.jpa.dao.data.ITagDefinitionDao; +import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptMapDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementTargetDao; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.model.entity.ModelConfig; @@ -23,7 +34,6 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; @@ -34,18 +44,62 @@ import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.UrlUtil; import org.apache.commons.io.IOUtils; import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.Search; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.AllergyIntolerance; +import org.hl7.fhir.dstu3.model.Appointment; +import org.hl7.fhir.dstu3.model.AuditEvent; +import org.hl7.fhir.dstu3.model.Binary; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.CarePlan; +import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.Communication; +import org.hl7.fhir.dstu3.model.CompartmentDefinition; +import org.hl7.fhir.dstu3.model.Composition; +import org.hl7.fhir.dstu3.model.ConceptMap; +import org.hl7.fhir.dstu3.model.Condition; +import org.hl7.fhir.dstu3.model.Consent; +import org.hl7.fhir.dstu3.model.Coverage; +import org.hl7.fhir.dstu3.model.Device; +import org.hl7.fhir.dstu3.model.DiagnosticReport; +import org.hl7.fhir.dstu3.model.Encounter; +import org.hl7.fhir.dstu3.model.Group; +import org.hl7.fhir.dstu3.model.Immunization; +import org.hl7.fhir.dstu3.model.ImmunizationRecommendation; +import org.hl7.fhir.dstu3.model.Location; +import org.hl7.fhir.dstu3.model.Media; +import org.hl7.fhir.dstu3.model.Medication; +import org.hl7.fhir.dstu3.model.MedicationAdministration; +import org.hl7.fhir.dstu3.model.MedicationRequest; +import org.hl7.fhir.dstu3.model.MedicationStatement; +import org.hl7.fhir.dstu3.model.Meta; +import org.hl7.fhir.dstu3.model.NamingSystem; +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.OperationDefinition; +import org.hl7.fhir.dstu3.model.Organization; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Practitioner; +import org.hl7.fhir.dstu3.model.PractitionerRole; +import org.hl7.fhir.dstu3.model.ProcedureRequest; +import org.hl7.fhir.dstu3.model.Questionnaire; +import org.hl7.fhir.dstu3.model.QuestionnaireResponse; +import org.hl7.fhir.dstu3.model.SearchParameter; +import org.hl7.fhir.dstu3.model.StructureDefinition; +import org.hl7.fhir.dstu3.model.Subscription; +import org.hl7.fhir.dstu3.model.Substance; +import org.hl7.fhir.dstu3.model.Task; +import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.junit.After; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDocumentDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDocumentDstu3Test.java index 6cfe5c59821..8cdb6e995af 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDocumentDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDocumentDstu3Test.java @@ -1,14 +1,13 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import java.nio.charset.StandardCharsets; - +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.hl7.fhir.dstu3.model.Bundle; import org.junit.AfterClass; import org.junit.Test; -import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; -import ca.uhn.fhir.util.TestUtil; +import java.nio.charset.StandardCharsets; public class FhirResourceDaoDocumentDstu3Test extends BaseJpaDstu3Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ExternalReferenceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ExternalReferenceTest.java index 262dc732fa0..68d72f3e0d8 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ExternalReferenceTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3ExternalReferenceTest.java @@ -1,14 +1,10 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - -import java.util.HashSet; -import java.util.Set; - +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.dstu3.model.Organization; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.instance.model.api.IIdType; @@ -17,11 +13,14 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; -import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.param.ReferenceParam; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.util.TestUtil; +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class FhirResourceDaoDstu3ExternalReferenceTest extends BaseJpaDstu3Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java index 1eb11e80011..ad30a24831f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3Test.java @@ -1,9 +1,8 @@ package ca.uhn.fhir.jpa.dao.dstu3; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; -import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; @@ -17,6 +16,7 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.SortOrderEnum; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.ParamPrefixEnum; @@ -36,14 +36,47 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomStringUtils; import org.hamcrest.Matchers; import org.hamcrest.core.StringContains; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Age; +import org.hl7.fhir.dstu3.model.Attachment; +import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; +import org.hl7.fhir.dstu3.model.CarePlan; +import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.CodeType; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.CompartmentDefinition; +import org.hl7.fhir.dstu3.model.ConceptMap; +import org.hl7.fhir.dstu3.model.Condition; +import org.hl7.fhir.dstu3.model.Consent; +import org.hl7.fhir.dstu3.model.DateTimeType; +import org.hl7.fhir.dstu3.model.DateType; +import org.hl7.fhir.dstu3.model.Device; +import org.hl7.fhir.dstu3.model.DiagnosticReport; +import org.hl7.fhir.dstu3.model.Encounter; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Meta; +import org.hl7.fhir.dstu3.model.NamingSystem; +import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; +import org.hl7.fhir.dstu3.model.OperationDefinition; +import org.hl7.fhir.dstu3.model.Organization; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Period; +import org.hl7.fhir.dstu3.model.Quantity; import org.hl7.fhir.dstu3.model.Quantity.QuantityComparator; +import org.hl7.fhir.dstu3.model.Questionnaire; +import org.hl7.fhir.dstu3.model.Range; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.SimpleQuantity; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.StructureDefinition; +import org.hl7.fhir.dstu3.model.Timing; +import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java index 4218f6e8c2b..ba2d5e406ed 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3Test.java @@ -24,14 +24,35 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Appointment; +import org.hl7.fhir.dstu3.model.Attachment; +import org.hl7.fhir.dstu3.model.Binary; +import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryResponseComponent; import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.Condition; +import org.hl7.fhir.dstu3.model.DateTimeType; +import org.hl7.fhir.dstu3.model.DiagnosticReport; +import org.hl7.fhir.dstu3.model.Encounter; +import org.hl7.fhir.dstu3.model.EpisodeOfCare; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Medication; +import org.hl7.fhir.dstu3.model.MedicationRequest; +import org.hl7.fhir.dstu3.model.Meta; +import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; +import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; +import org.hl7.fhir.dstu3.model.Organization; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Quantity; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeHookTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeHookTest.java index 7fb585a7b4d..cbfc1973245 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeHookTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/ExpungeHookTest.java @@ -3,10 +3,9 @@ package ca.uhn.fhir.jpa.dao.expunge; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoPatient; import ca.uhn.fhir.jpa.api.model.ExpungeOptions; -import ca.uhn.fhir.jpa.config.TestDstu3Config; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.test.concurrency.PointcutLatch; @@ -15,10 +14,7 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunnerTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunnerTest.java index c96f5e18d18..4535383bd3a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunnerTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunnerTest.java @@ -1,8 +1,8 @@ package ca.uhn.fhir.jpa.dao.expunge; import ca.uhn.fhir.interceptor.api.HookParams; -import ca.uhn.fhir.jpa.config.TestDstu3Config; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.config.TestDstu3Config; import ca.uhn.test.concurrency.PointcutLatch; import com.google.common.collect.Sets; import org.apache.commons.lang3.builder.ToStringBuilder; 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 7d50ab12a21..478c7c90b27 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 @@ -13,7 +13,6 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider; import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; @@ -79,6 +78,7 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.BasePagingProvider; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import ca.uhn.fhir.test.utilities.ITestDataBuilder; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.UrlUtil; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java index 174c7cd327c..d9fc6ddda69 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java @@ -4,8 +4,8 @@ import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.executor.InterceptorService; -import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.SortOrderEnum; @@ -39,7 +39,9 @@ import java.util.stream.Collectors; import static junit.framework.TestCase.assertTrue; import static org.apache.commons.lang3.StringUtils.leftPad; import static org.hamcrest.Matchers.greaterThan; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import static org.mockito.Mockito.when; @SuppressWarnings("unchecked") diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoDocumentR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoDocumentR4Test.java index 4caf3942cf6..5156e2824d9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoDocumentR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoDocumentR4Test.java @@ -1,14 +1,13 @@ package ca.uhn.fhir.jpa.dao.r4; -import java.nio.charset.StandardCharsets; - +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.hl7.fhir.r4.model.Bundle; import org.junit.AfterClass; import org.junit.Test; -import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; -import ca.uhn.fhir.util.TestUtil; +import java.nio.charset.StandardCharsets; public class FhirResourceDaoDocumentR4Test extends BaseJpaR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CacheWarmingTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CacheWarmingTest.java index f6f956fc470..4bdb76816bd 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CacheWarmingTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CacheWarmingTest.java @@ -2,10 +2,10 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.api.model.WarmCacheEntry; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl; -import ca.uhn.fhir.jpa.api.model.WarmCacheEntry; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.StringParam; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ConceptMapTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ConceptMapTest.java index 00a88ecfe44..58b58024f42 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ConceptMapTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ConceptMapTest.java @@ -5,8 +5,11 @@ import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.jpa.api.model.TranslationResult; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; +import org.hl7.fhir.r4.model.UriType; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; @@ -19,7 +22,9 @@ import org.springframework.transaction.support.TransactionTemplate; import java.io.IOException; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class FhirResourceDaoR4ConceptMapTest extends BaseJpaR4Test { private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4ConceptMapTest.class); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ExternalReferenceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ExternalReferenceTest.java index e28dd70b6a5..fa82ef36355 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ExternalReferenceTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ExternalReferenceTest.java @@ -1,27 +1,26 @@ package ca.uhn.fhir.jpa.dao.r4; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - -import java.util.HashSet; -import java.util.Set; - -import org.hl7.fhir.r4.model.Organization; -import org.hl7.fhir.r4.model.Patient; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; - import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.TestUtil; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class FhirResourceDaoR4ExternalReferenceTest extends BaseJpaR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java index 1036a833899..ddb982d5569 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java @@ -10,16 +10,51 @@ import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.NumberParam; +import ca.uhn.fhir.rest.param.ReferenceOrListParam; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import org.hamcrest.Matchers; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Appointment; import org.hl7.fhir.r4.model.Appointment.AppointmentStatus; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.ChargeItem; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Composition; +import org.hl7.fhir.r4.model.Condition; +import org.hl7.fhir.r4.model.DateTimeType; +import org.hl7.fhir.r4.model.DateType; +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.DiagnosticReport; +import org.hl7.fhir.r4.model.Encounter; +import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; -import org.junit.*; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.Group; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.MedicationRequest; +import org.hl7.fhir.r4.model.MessageHeader; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Practitioner; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.SearchParameter; +import org.hl7.fhir.r4.model.ServiceRequest; +import org.hl7.fhir.r4.model.Specimen; +import org.hl7.fhir.r4.model.StringType; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.internal.util.collections.ListUtil; import org.springframework.transaction.TransactionStatus; @@ -29,11 +64,20 @@ import org.springframework.transaction.support.TransactionTemplate; import java.util.List; -import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchCustomSearchParamTest.class); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java index ac268794c94..50e13882a58 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java @@ -3,10 +3,21 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.*; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.QuantityParam; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.DateType; +import org.hl7.fhir.r4.model.MedicationRequest; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Task; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; @@ -14,8 +25,13 @@ import org.slf4j.LoggerFactory; import java.util.List; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInRelativeOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test { private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4SearchMissingTest.class); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java index 81c8476c782..b5832777f82 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java @@ -4,7 +4,6 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.entity.Search; -import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; @@ -23,7 +22,11 @@ import ca.uhn.fhir.util.TestUtil; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.DateTimeType; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -41,8 +44,16 @@ import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.leftPad; import static org.awaitility.Awaitility.await; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.matchesPattern; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java index 6f3c98c806b..1ce3057a2bb 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchTest.java @@ -1,20 +1,20 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.config.TestR4ConfigWithElasticSearch; import ca.uhn.fhir.jpa.dao.BaseJpaTest; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java index 618272a8a1a..02eb8159ebc 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java @@ -2,15 +2,15 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.config.TestR4WithLuceneDisabledConfig; import ca.uhn.fhir.jpa.dao.BaseJpaTest; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; -import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; @@ -27,7 +27,22 @@ import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.AllergyIntolerance; +import org.hl7.fhir.r4.model.Appointment; +import org.hl7.fhir.r4.model.AuditEvent; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CarePlan; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CompartmentDefinition; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.Condition; +import org.hl7.fhir.r4.model.Device; +import org.hl7.fhir.r4.model.DiagnosticReport; +import org.hl7.fhir.r4.model.Encounter; +import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.ValueSet; import org.junit.AfterClass; import org.junit.Before; import org.junit.Ignore; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java index 79ee4a95d93..9756a8bc560 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java @@ -21,12 +21,26 @@ import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.AllergyIntolerance; import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCategory; +import org.hl7.fhir.r4.model.AuditEvent; +import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.r4.model.ValueSet.*; -import org.junit.*; +import org.hl7.fhir.r4.model.InstantType; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceComponent; +import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r4.model.ValueSet.FilterOperator; +import org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent; +import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; import java.util.ArrayList; import java.util.Date; @@ -34,8 +48,13 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsStringIgnoringCase; +import static org.hamcrest.Matchers.empty; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; @SuppressWarnings("Duplicates") public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java index a285b77431d..26685134fe2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java @@ -16,16 +16,43 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; 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.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.InstantType; +import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Resource; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; -import java.util.*; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TimeZone; +import java.util.UUID; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.eq; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.matchesPattern; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4UpdateTest.class); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java index c1ce1e67309..5c0fd454a50 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValueSetTest.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.TestUtil; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4Test.java index 7548c0c0fd4..d8927950832 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4Test.java @@ -43,7 +43,7 @@ public class FhirResourceDaoSearchParameterR4Test { @Test public void testValidateAllBuiltInSearchParams() { - for (String nextResource : myCtx.getResourceNames()) { + for (String nextResource : myCtx.getResourceTypes()) { RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(nextResource); for (RuntimeSearchParam nextp : nextResDef.getSearchParams()) { if (nextp.getName().equals("_id")) { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java index 5d51ec64887..2c05b68138b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java @@ -1,8 +1,13 @@ package ca.uhn.fhir.jpa.dao.r4; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; +import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum; +import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.entity.ResourceTag; +import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; @@ -12,17 +17,54 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.hamcrest.Matchers; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.Bundle.*; +import org.hl7.fhir.r4.model.AllergyIntolerance; +import org.hl7.fhir.r4.model.Appointment; +import org.hl7.fhir.r4.model.Attachment; +import org.hl7.fhir.r4.model.Binary; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Bundle.BundleEntryRequestComponent; +import org.hl7.fhir.r4.model.Bundle.BundleEntryResponseComponent; +import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.Bundle.HTTPVerb; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Condition; +import org.hl7.fhir.r4.model.DateTimeType; +import org.hl7.fhir.r4.model.DiagnosticReport; +import org.hl7.fhir.r4.model.Encounter; +import org.hl7.fhir.r4.model.EpisodeOfCare; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Medication; +import org.hl7.fhir.r4.model.MedicationRequest; +import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; -import org.junit.*; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Practitioner; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.ValueSet; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; @@ -34,11 +76,30 @@ import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.matchesPattern; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java index 40d3bf82f7c..fdd69f0a730 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao.r5; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; @@ -12,14 +13,37 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoStructureDefinition; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSubscription; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider; import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc; import ca.uhn.fhir.jpa.config.TestR5Config; import ca.uhn.fhir.jpa.dao.BaseJpaTest; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; -import ca.uhn.fhir.jpa.dao.data.*; +import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; +import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedCompositeStringUniqueDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamQuantityDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao; +import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao; +import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; +import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; +import ca.uhn.fhir.jpa.dao.data.ISearchDao; +import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao; +import ca.uhn.fhir.jpa.dao.data.ITagDefinitionDao; +import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; +import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptDesignationDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptMapDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptMapGroupElementTargetDao; +import ca.uhn.fhir.jpa.dao.data.ITermConceptParentChildLinkDao; +import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDao; +import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDesignationDao; +import ca.uhn.fhir.jpa.dao.data.ITermValueSetDao; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; import ca.uhn.fhir.jpa.interceptor.PerformanceTracingLoggingInterceptor; import ca.uhn.fhir.jpa.model.entity.ModelConfig; @@ -27,7 +51,6 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.provider.r5.JpaSystemProviderR5; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc; @@ -39,13 +62,13 @@ import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.BasePagingProvider; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.validation.FhirValidator; @@ -55,10 +78,58 @@ import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.Search; import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.model.*; +import org.hl7.fhir.r5.model.AllergyIntolerance; +import org.hl7.fhir.r5.model.Appointment; +import org.hl7.fhir.r5.model.AuditEvent; +import org.hl7.fhir.r5.model.Binary; +import org.hl7.fhir.r5.model.Bundle; +import org.hl7.fhir.r5.model.CarePlan; +import org.hl7.fhir.r5.model.ChargeItem; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.model.CodeableConcept; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.Communication; +import org.hl7.fhir.r5.model.CommunicationRequest; +import org.hl7.fhir.r5.model.CompartmentDefinition; +import org.hl7.fhir.r5.model.ConceptMap; import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent; import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent; import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent; +import org.hl7.fhir.r5.model.Condition; +import org.hl7.fhir.r5.model.Consent; +import org.hl7.fhir.r5.model.Coverage; +import org.hl7.fhir.r5.model.Device; +import org.hl7.fhir.r5.model.DiagnosticReport; +import org.hl7.fhir.r5.model.Encounter; +import org.hl7.fhir.r5.model.Enumerations; +import org.hl7.fhir.r5.model.Group; +import org.hl7.fhir.r5.model.Immunization; +import org.hl7.fhir.r5.model.ImmunizationRecommendation; +import org.hl7.fhir.r5.model.Location; +import org.hl7.fhir.r5.model.Medication; +import org.hl7.fhir.r5.model.MedicationAdministration; +import org.hl7.fhir.r5.model.MedicationRequest; +import org.hl7.fhir.r5.model.Meta; +import org.hl7.fhir.r5.model.MolecularSequence; +import org.hl7.fhir.r5.model.NamingSystem; +import org.hl7.fhir.r5.model.Observation; +import org.hl7.fhir.r5.model.OperationDefinition; +import org.hl7.fhir.r5.model.Organization; +import org.hl7.fhir.r5.model.Patient; +import org.hl7.fhir.r5.model.Practitioner; +import org.hl7.fhir.r5.model.PractitionerRole; +import org.hl7.fhir.r5.model.Procedure; +import org.hl7.fhir.r5.model.Questionnaire; +import org.hl7.fhir.r5.model.QuestionnaireResponse; +import org.hl7.fhir.r5.model.RiskAssessment; +import org.hl7.fhir.r5.model.SearchParameter; +import org.hl7.fhir.r5.model.ServiceRequest; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.Subscription; +import org.hl7.fhir.r5.model.Substance; +import org.hl7.fhir.r5.model.Task; +import org.hl7.fhir.r5.model.UriType; +import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.IResourceValidator; import org.junit.After; import org.junit.AfterClass; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/StorageInterceptorEventsR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/StorageInterceptorEventsR5Test.java index 33176439681..fbea0a3c77c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/StorageInterceptorEventsR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r5/StorageInterceptorEventsR5Test.java @@ -1,10 +1,10 @@ package ca.uhn.fhir.jpa.dao.r5; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceR4Test.java index 9175d898e40..3edddea4da1 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceR4Test.java @@ -2,9 +2,9 @@ package ca.uhn.fhir.jpa.delete; import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.model.DeleteConflict; import ca.uhn.fhir.jpa.api.model.DeleteConflictList; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; -import ca.uhn.fhir.jpa.api.model.DeleteConflict; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import org.hl7.fhir.instance.model.api.IIdType; @@ -23,7 +23,9 @@ import java.util.Iterator; import java.util.List; import java.util.function.Function; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; public class DeleteConflictServiceR4Test extends BaseJpaR4Test { private static final Logger ourLog = LoggerFactory.getLogger(DeleteConflictServiceR4Test.class); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceTest.java index 4ec264b7860..ff1c2941d0b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/delete/DeleteConflictServiceTest.java @@ -2,8 +2,8 @@ package ca.uhn.fhir.jpa.delete; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; -import ca.uhn.fhir.jpa.api.model.DeleteConflictList; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.model.DeleteConflictList; import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ResourceLink; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/partition/PartitionManagementProviderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/partition/PartitionManagementProviderTest.java index 23aa520a6ae..0eb7df13e2b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/partition/PartitionManagementProviderTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/partition/PartitionManagementProviderTest.java @@ -2,10 +2,10 @@ package ca.uhn.fhir.jpa.partition; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.entity.PartitionEntity; -import ca.uhn.fhir.jpa.model.util.ProviderConstants; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; import ca.uhn.fhir.test.utilities.server.RestfulServerRule; import org.hl7.fhir.r4.model.CodeType; import org.hl7.fhir.r4.model.IntegerType; 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 15ea7c88cf6..e877f13d1dc 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 @@ -1,8 +1,8 @@ package ca.uhn.fhir.jpa.provider; -import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.model.dstu2.resource.Patient; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java index 8162df8355a..0cd97fc7eeb 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java @@ -1,66 +1,8 @@ package ca.uhn.fhir.jpa.provider; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsInRelativeOrder; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.emptyString; -import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.startsWith; -import static org.hamcrest.Matchers.stringContainsInOrder; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketTimeoutException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -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.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.springframework.test.util.AopTestUtils; - -import com.google.common.base.Charsets; - import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; @@ -126,6 +68,62 @@ import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.UrlUtil; +import com.google.common.base.Charsets; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +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.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.test.util.AopTestUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsInRelativeOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.startsWith; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderExpungeDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderExpungeDstu2Test.java index 731b5e96b93..9b8541c08ae 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderExpungeDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderExpungeDstu2Test.java @@ -1,8 +1,8 @@ package ca.uhn.fhir.jpa.provider; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.model.ExpungeOptions; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.model.dstu2.resource.Observation; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.dstu2.valueset.ObservationStatusEnum; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderTransactionSearchDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderTransactionSearchDstu2Test.java index a19c766dd19..c98215523b9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderTransactionSearchDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderTransactionSearchDstu2Test.java @@ -16,6 +16,7 @@ import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.util.TestUtil; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -28,10 +29,11 @@ import org.junit.Test; import java.util.ArrayList; import java.util.List; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import ca.uhn.fhir.test.utilities.JettyUtil; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; public class SystemProviderTransactionSearchDstu2Test extends BaseJpaDstu2Test { 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 22a625b873a..1dc0356cc9f 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 @@ -1,14 +1,14 @@ package ca.uhn.fhir.jpa.provider.dstu3; import ca.uhn.fhir.context.support.IValidationSupport; -import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; +import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.EncodingEnum; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/CompositionDocumentDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/CompositionDocumentDstu3Test.java index 6ed15342fb3..d9d43ec1121 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/CompositionDocumentDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/CompositionDocumentDstu3Test.java @@ -9,17 +9,32 @@ import org.apache.commons.io.IOUtils; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.dstu3.model.Composition; +import org.hl7.fhir.dstu3.model.Encounter; import org.hl7.fhir.dstu3.model.Encounter.EncounterStatus; +import org.hl7.fhir.dstu3.model.ListResource; +import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; -import org.junit.*; +import org.hl7.fhir.dstu3.model.Organization; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Reference; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; public class CompositionDocumentDstu3Test extends BaseResourceProviderDstu3Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java index fd6d25e5226..e260c82933e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; @@ -23,9 +23,16 @@ 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.dstu3.model.*; +import org.hl7.fhir.dstu3.model.BooleanType; +import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.dstu3.model.CodeType; +import org.hl7.fhir.dstu3.model.IntegerType; +import org.hl7.fhir.dstu3.model.Parameters; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.UriType; +import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator; import org.hl7.fhir.instance.model.api.IIdType; @@ -44,7 +51,11 @@ import java.util.List; import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3TerminologyTest.URL_MY_CODE_SYSTEM; import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3TerminologyTest.URL_MY_VALUE_SET; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderExpungeDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderExpungeDstu3Test.java index 9a957a09a5b..34c487da76f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderExpungeDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderExpungeDstu3Test.java @@ -1,8 +1,8 @@ package ca.uhn.fhir.jpa.provider.dstu3; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.model.ExpungeOptions; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderTransactionSearchDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderTransactionSearchDstu3Test.java index 7b783d1d9e3..42c2621e9e5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderTransactionSearchDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderTransactionSearchDstu3Test.java @@ -1,15 +1,18 @@ package ca.uhn.fhir.jpa.provider.dstu3; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; +import ca.uhn.fhir.jpa.rp.dstu3.ObservationResourceProvider; +import ca.uhn.fhir.jpa.rp.dstu3.OrganizationResourceProvider; +import ca.uhn.fhir.jpa.rp.dstu3.PatientResourceProvider; import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.test.utilities.JettyUtil; +import ca.uhn.fhir.util.TestUtil; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; @@ -22,22 +25,32 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 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.dstu3.model.Bundle.*; +import org.hl7.fhir.dstu3.model.Binary; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.dstu3.model.Bundle.BundleType; +import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; -import ca.uhn.fhir.jpa.rp.dstu3.*; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.test.utilities.JettyUtil; -import ca.uhn.fhir.util.TestUtil; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; public class SystemProviderTransactionSearchDstu3Test extends BaseJpaDstu3Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorJpaR4Test.java index bb2fbc561c1..a9ca9726609 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorJpaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorJpaR4Test.java @@ -1,7 +1,6 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; -import ca.uhn.fhir.jpa.model.util.ProviderConstants; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.MethodOutcome; @@ -16,6 +15,7 @@ import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule; import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRuleTester; import ca.uhn.fhir.rest.server.interceptor.auth.PolicyEnum; import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; import ca.uhn.fhir.util.TestUtil; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseMultitenantResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseMultitenantResourceProviderR4Test.java index 20068415158..a7598137a0b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseMultitenantResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BaseMultitenantResourceProviderR4Test.java @@ -2,7 +2,6 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.config.PartitionSettings; -import ca.uhn.fhir.jpa.model.util.ProviderConstants; import ca.uhn.fhir.jpa.partition.PartitionManagementProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; @@ -12,6 +11,7 @@ import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor; import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule; import ca.uhn.fhir.rest.server.interceptor.auth.PolicyEnum; import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy; import ca.uhn.fhir.test.utilities.ITestDataBuilder; import org.hl7.fhir.instance.model.api.IBaseResource; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryAccessProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryAccessProviderR4Test.java index 8d8c4b2dfcc..0fdaf037424 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryAccessProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryAccessProviderR4Test.java @@ -3,9 +3,9 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc; import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -18,7 +18,12 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Attachment; +import org.hl7.fhir.r4.model.Binary; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.DocumentReference; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.StringType; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -31,8 +36,19 @@ import java.io.IOException; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.matchesPattern; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryStorageInterceptorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryStorageInterceptorR4Test.java index 6cbb95bd338..0522794c457 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryStorageInterceptorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/BinaryStorageInterceptorR4Test.java @@ -1,10 +1,10 @@ package ca.uhn.fhir.jpa.provider.r4; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc; import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl; -import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.instance.model.api.IIdType; @@ -19,8 +19,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.blankOrNullString; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class BinaryStorageInterceptorR4Test extends BaseResourceProviderR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ConsentInterceptorResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ConsentInterceptorResourceProviderR4Test.java index 848561b02d5..fe89fad7457 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ConsentInterceptorResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ConsentInterceptorResourceProviderR4Test.java @@ -1,15 +1,20 @@ package ca.uhn.fhir.jpa.provider.r4; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.config.BaseConfig; import ca.uhn.fhir.jpa.config.TestR4Config; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.PreferReturnEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import ca.uhn.fhir.rest.server.interceptor.consent.*; +import ca.uhn.fhir.rest.server.interceptor.consent.ConsentInterceptor; +import ca.uhn.fhir.rest.server.interceptor.consent.ConsentOperationStatusEnum; +import ca.uhn.fhir.rest.server.interceptor.consent.ConsentOutcome; +import ca.uhn.fhir.rest.server.interceptor.consent.DelegatingConsentService; +import ca.uhn.fhir.rest.server.interceptor.consent.IConsentContextServices; +import ca.uhn.fhir.rest.server.interceptor.consent.IConsentService; import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.UrlUtil; import com.google.common.base.Charsets; @@ -26,7 +31,12 @@ import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -49,7 +59,11 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.blankOrNullString; import static org.hamcrest.Matchers.matchesPattern; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/DiffProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/DiffProviderR4Test.java index 1d3b7327897..f793d39cd28 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/DiffProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/DiffProviderR4Test.java @@ -1,9 +1,8 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; -import ca.uhn.fhir.jpa.model.util.ProviderConstants; -import ca.uhn.fhir.model.primitive.BooleanDt; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.HumanName; @@ -18,7 +17,6 @@ import org.slf4j.LoggerFactory; import static ca.uhn.fhir.jpa.patch.FhirPatchApplyR4Test.extractPartValue; import static ca.uhn.fhir.jpa.patch.FhirPatchApplyR4Test.extractPartValuePrimitive; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; public class DiffProviderR4Test extends BaseResourceProviderR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/EmptyIndexesR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/EmptyIndexesR4Test.java index 6d1d76ac695..482c3fa6308 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/EmptyIndexesR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/EmptyIndexesR4Test.java @@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.util.TestUtil; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -17,15 +18,17 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 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.junit.*; +import org.hl7.fhir.r4.model.Observation; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; import java.util.concurrent.TimeUnit; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import ca.uhn.fhir.test.utilities.JettyUtil; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertThat; public class EmptyIndexesR4Test extends BaseJpaR4Test { private static RestfulServer myRestServer; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java index e19f03668e7..9eb8287f205 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java @@ -1,13 +1,13 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.search.PersistedJpaSearchFirstPageBundleProvider; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; @@ -35,7 +35,9 @@ import java.util.List; import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.not; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class ExpungeR4Test extends BaseResourceProviderR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatientEverythingR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatientEverythingR4Test.java index 248791983b3..0d8d9c6ac3b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatientEverythingR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/PatientEverythingR4Test.java @@ -1,27 +1,40 @@ package ca.uhn.fhir.jpa.provider.r4; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import java.io.IOException; -import java.util.*; - -import org.apache.commons.io.IOUtils; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; -import org.hl7.fhir.r4.model.Encounter.EncounterStatus; -import org.hl7.fhir.r4.model.Observation.ObservationStatus; -import org.junit.*; - -import com.google.common.base.Charsets; - import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.util.TestUtil; +import com.google.common.base.Charsets; +import org.apache.commons.io.IOUtils; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Encounter; +import org.hl7.fhir.r4.model.Encounter.EncounterStatus; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Set; +import java.util.TreeSet; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; @SuppressWarnings("Duplicates") public class PatientEverythingR4Test extends BaseResourceProviderR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java index 29da7c09c66..4d7b3b6514d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java @@ -2,8 +2,8 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Pointcut; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.entity.ModelConfig; @@ -23,12 +23,20 @@ import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Appointment; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CapabilityStatement; import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestComponent; import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestResourceComponent; import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent; +import org.hl7.fhir.r4.model.CodeType; import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Practitioner; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.SearchParameter; import org.hl7.fhir.r4.model.SearchParameter.XPathUsageType; import org.junit.After; import org.junit.AfterClass; @@ -46,7 +54,6 @@ import java.util.stream.Collectors; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.in; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderExpungeR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderExpungeR4Test.java index de83fbc8689..a46005774b9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderExpungeR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderExpungeR4Test.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; 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 af1ad25e85d..38de65c7327 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 @@ -13,12 +13,8 @@ import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.api.server.ResponseDetails; -import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; -import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; -import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter; import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.TestUtil; @@ -30,10 +26,14 @@ import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.BundleType; import org.hl7.fhir.r4.model.Bundle.HTTPVerb; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; import org.junit.After; import org.junit.AfterClass; import org.junit.Test; @@ -42,8 +42,6 @@ import org.mockito.Captor; import org.mockito.Mock; import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -53,9 +51,17 @@ import java.util.List; import static org.apache.commons.lang3.time.DateUtils.MILLIS_PER_SECOND; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index d84e46ecce3..ec07c67be5a 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; @@ -14,7 +14,10 @@ import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.PreferReturnEnum; +import ca.uhn.fhir.rest.api.SearchTotalModeEnum; +import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.client.apache.ResourceEntity; import ca.uhn.fhir.rest.client.api.IClientInterceptor; import ca.uhn.fhir.rest.client.api.IGenericClient; @@ -22,8 +25,17 @@ import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.api.IHttpResponse; import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; import ca.uhn.fhir.rest.gclient.StringClientParam; -import ca.uhn.fhir.rest.param.*; -import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.NumberParam; +import ca.uhn.fhir.rest.param.ParamPrefixEnum; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringOrListParam; +import ca.uhn.fhir.rest.param.StringParam; +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.ResourceGoneException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.UrlUtil; @@ -35,26 +47,92 @@ import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.*; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicNameValuePair; import org.hamcrest.Matchers; -import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; -import org.hl7.fhir.r4.model.*; -import org.hl7.fhir.r4.model.Bundle.*; +import org.hl7.fhir.instance.model.api.IAnyResource; +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.Attachment; +import org.hl7.fhir.r4.model.AuditEvent; +import org.hl7.fhir.r4.model.Basic; +import org.hl7.fhir.r4.model.Binary; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Bundle.BundleLinkComponent; +import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.Bundle.HTTPVerb; +import org.hl7.fhir.r4.model.Bundle.SearchEntryMode; +import org.hl7.fhir.r4.model.CarePlan; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Condition; +import org.hl7.fhir.r4.model.DateTimeType; +import org.hl7.fhir.r4.model.DateType; +import org.hl7.fhir.r4.model.Device; +import org.hl7.fhir.r4.model.DiagnosticReport; +import org.hl7.fhir.r4.model.DocumentManifest; +import org.hl7.fhir.r4.model.DocumentReference; +import org.hl7.fhir.r4.model.Encounter; import org.hl7.fhir.r4.model.Encounter.EncounterLocationComponent; import org.hl7.fhir.r4.model.Encounter.EncounterStatus; +import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.Group; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.ImagingStudy; +import org.hl7.fhir.r4.model.InstantType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Location; +import org.hl7.fhir.r4.model.Media; +import org.hl7.fhir.r4.model.Medication; +import org.hl7.fhir.r4.model.MedicationAdministration; +import org.hl7.fhir.r4.model.MedicationRequest; +import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.MolecularSequence; +import org.hl7.fhir.r4.model.Narrative; import org.hl7.fhir.r4.model.Narrative.NarrativeStatus; +import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Observation.ObservationStatus; +import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Period; +import org.hl7.fhir.r4.model.Practitioner; +import org.hl7.fhir.r4.model.Procedure; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.Questionnaire; import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType; +import org.hl7.fhir.r4.model.QuestionnaireResponse; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.SearchParameter; +import org.hl7.fhir.r4.model.ServiceRequest; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.Subscription; import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType; import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus; +import org.hl7.fhir.r4.model.UnsignedIntType; +import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.utilities.xhtml.XhtmlNode; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.util.AopTestUtils; import org.springframework.transaction.TransactionStatus; @@ -69,14 +147,44 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; import java.util.stream.Collectors; import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast; import static ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsInRelativeOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.lessThan; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.hamcrest.Matchers.matchesPattern; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.startsWith; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; @SuppressWarnings("Duplicates") public class ResourceProviderR4Test extends BaseResourceProviderR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java index b19ef2dc11d..fc09fbf80d1 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.provider.r4; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; @@ -26,9 +26,19 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.UriType; +import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r4.model.ValueSet.FilterOperator; import org.hl7.fhir.r4.model.codesystems.HttpVerb; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderTransactionSearchR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderTransactionSearchR4Test.java index a36cb5e6cd9..910d03324e4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderTransactionSearchR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderTransactionSearchR4Test.java @@ -2,15 +2,20 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; -import ca.uhn.fhir.jpa.rp.r4.*; +import ca.uhn.fhir.jpa.rp.r4.MedicationRequestResourceProvider; +import ca.uhn.fhir.jpa.rp.r4.MedicationResourceProvider; +import ca.uhn.fhir.jpa.rp.r4.ObservationResourceProvider; +import ca.uhn.fhir.jpa.rp.r4.OrganizationResourceProvider; +import ca.uhn.fhir.jpa.rp.r4.PatientResourceProvider; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.util.TestUtil; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -18,11 +23,16 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 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.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.BundleType; import org.hl7.fhir.r4.model.Bundle.HTTPVerb; import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Medication; +import org.hl7.fhir.r4.model.MedicationRequest; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -32,10 +42,11 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import ca.uhn.fhir.test.utilities.JettyUtil; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; public class SystemProviderTransactionSearchR4Test extends BaseJpaR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/BaseResourceProviderR5Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/BaseResourceProviderR5Test.java index 576d5709362..a112314f98d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/BaseResourceProviderR5Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/BaseResourceProviderR5Test.java @@ -2,15 +2,15 @@ package ca.uhn.fhir.jpa.provider.r5; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; -import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.dao.r5.BaseJpaR5Test; import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; -import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionMatcherInterceptor; +import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader; +import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionMatcherInterceptor; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.parser.StrictErrorHandler; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java index 7679cdac434..b7dd83f9dc9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.provider.r5; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; @@ -27,10 +27,20 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r5.model.*; +import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.Bundle; import org.hl7.fhir.r5.model.Bundle.HTTPVerb; +import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.r5.model.CodeType; +import org.hl7.fhir.r5.model.Enumerations; +import org.hl7.fhir.r5.model.IdType; +import org.hl7.fhir.r5.model.IntegerType; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.StringType; +import org.hl7.fhir.r5.model.UriType; +import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; import org.junit.After; import org.junit.AfterClass; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PagingMultinodeProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PagingMultinodeProviderDstu3Test.java index 3f344d549e5..a76fde4a29c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PagingMultinodeProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/PagingMultinodeProviderDstu3Test.java @@ -1,9 +1,9 @@ package ca.uhn.fhir.jpa.search; -import static org.apache.commons.lang3.StringUtils.leftPad; -import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.assertThat; - +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; +import ca.uhn.fhir.parser.StrictErrorHandler; +import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Patient; import org.junit.After; @@ -11,10 +11,9 @@ import org.junit.AfterClass; import org.junit.Test; import org.springframework.test.util.AopTestUtils; -import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; -import ca.uhn.fhir.parser.StrictErrorHandler; -import ca.uhn.fhir.util.TestUtil; +import static org.apache.commons.lang3.StringUtils.leftPad; +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThat; public class PagingMultinodeProviderDstu3Test extends BaseResourceProviderDstu3Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/r4/PagingMultinodeProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/r4/PagingMultinodeProviderR4Test.java index c65205736dc..afb308149aa 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/r4/PagingMultinodeProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/r4/PagingMultinodeProviderR4Test.java @@ -1,19 +1,20 @@ package ca.uhn.fhir.jpa.search.r4; -import static org.apache.commons.lang3.StringUtils.leftPad; -import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.assertThat; - -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Patient; -import org.junit.*; -import org.springframework.test.util.AopTestUtils; - import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.util.TestUtil; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Patient; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Test; +import org.springframework.test.util.AopTestUtils; + +import static org.apache.commons.lang3.StringUtils.leftPad; +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThat; public class PagingMultinodeProviderR4Test extends BaseResourceProviderR4Test { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java index f1fc6df2f0c..f6c814a52d5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java @@ -1,11 +1,11 @@ package ca.uhn.fhir.jpa.search.reindex; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseJpaTest; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/StressTestR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/StressTestR4Test.java index fd26081c3c8..e0ab52cae23 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/StressTestR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/StressTestR4Test.java @@ -1,15 +1,15 @@ package ca.uhn.fhir.jpa.stresstest; -import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; @@ -22,26 +22,45 @@ import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.hamcrest.Matchers; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleType; import org.hl7.fhir.r4.model.Bundle.HTTPVerb; -import org.junit.*; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.DateTimeType; +import org.hl7.fhir.r4.model.DiagnosticReport; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.ListResource; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Resource; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.util.AopTestUtils; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.leftPad; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.not; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java index 7a35525ca0c..8d69ee5d3d7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java @@ -28,7 +28,12 @@ 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.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.Collections; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java index 72ea14e1e99..fbfd1b34ab5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java @@ -4,7 +4,6 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.model.util.ProviderConstants; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil; import ca.uhn.fhir.jpa.subscription.triggering.ISubscriptionTriggeringSvc; @@ -17,6 +16,7 @@ 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.provider.ProviderConstants; import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.test.utilities.ProxyUtil; import com.google.common.collect.Lists; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java index db5a88b5698..dd4abe50c7f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java @@ -1,8 +1,8 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.support.IValidationSupport; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.util.TestUtil; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java index c0430daf501..464cc6ebfed 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java @@ -31,7 +31,6 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.countMatches; import static org.apache.commons.lang3.StringUtils.leftPad; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.Matchers.matchesPattern; diff --git a/hapi-fhir-jpaserver-empi/pom.xml b/hapi-fhir-jpaserver-empi/pom.xml new file mode 100644 index 00000000000..7aad00751b1 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + ca.uhn.hapi.fhir + hapi-deployable-pom + 5.1.0-SNAPSHOT + ../hapi-deployable-pom/pom.xml + + + hapi-fhir-jpaserver-empi + jar + + HAPI FHIR JPA Server - Enterprise Master Patient Index + + + + ca.uhn.hapi.fhir + hapi-fhir-server-empi + ${project.version} + + + org.springframework.data + spring-data-jpa + ${spring_data_version} + + + org.hamcrest + java-hamcrest + test + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-base + ${project.version} + + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-test-utilities + ${project.version} + test + + + org.awaitility + awaitility + test + + + + + + + + org.apache.maven.plugins + maven-site-plugin + + true + + + + + + + org.jacoco + jacoco-maven-plugin + + + default-prepare-agent + + prepare-agent + + + + + + + diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/broker/EmpiMessageHandler.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/broker/EmpiMessageHandler.java new file mode 100644 index 00000000000..b4289a2ca82 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/broker/EmpiMessageHandler.java @@ -0,0 +1,139 @@ +package ca.uhn.fhir.jpa.empi.broker; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.log.Logs; +import ca.uhn.fhir.empi.model.EmpiTransactionContext; +import ca.uhn.fhir.empi.util.EmpiUtil; +import ca.uhn.fhir.interceptor.api.HookParams; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.empi.svc.EmpiMatchLinkSvc; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; +import ca.uhn.fhir.rest.server.TransactionLogMessages; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.slf4j.Logger; +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 EmpiMessageHandler implements MessageHandler { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + private EmpiMatchLinkSvc myEmpiMatchLinkSvc; + @Autowired + private IInterceptorBroadcaster myInterceptorBroadcaster; + @Autowired + private FhirContext myFhirContext; + + @Override + public void handleMessage(Message theMessage) throws MessagingException { + ourLog.info("Handling resource modified message: {}", theMessage); + + if (!(theMessage instanceof ResourceModifiedJsonMessage)) { + ourLog.warn("Unexpected message payload type: {}", theMessage); + return; + } + + ResourceModifiedMessage msg = ((ResourceModifiedJsonMessage) theMessage).getPayload(); + try { + matchEmpiAndUpdateLinks(msg); + } catch (Exception e) { + ourLog.error("Failed to handle EMPI Matching Resource:", e); + throw e; + } + } + + public void matchEmpiAndUpdateLinks(ResourceModifiedMessage theMsg) { + String resourceType = theMsg.getId(myFhirContext).getResourceType(); + validateResourceType(resourceType); + EmpiTransactionContext empiContext = createEmpiContext(theMsg); + try { + switch (theMsg.getOperationType()) { + case CREATE: + handleCreatePatientOrPractitioner(theMsg, empiContext); + break; + case UPDATE: + handleUpdatePatientOrPractitioner(theMsg, empiContext); + break; + case DELETE: + default: + ourLog.trace("Not processing modified message for {}", theMsg.getOperationType()); + } + }catch (Exception e) { + log(empiContext, "Failure during EMPI processing: " + e.getMessage()); + } finally { + // Interceptor call: EMPI_AFTER_PERSISTED_RESOURCE_CHECKED + HookParams params = new HookParams() + .add(ResourceModifiedMessage.class, theMsg) + .add(TransactionLogMessages.class, empiContext.getTransactionLogMessages()); + myInterceptorBroadcaster.callHooks(Pointcut.EMPI_AFTER_PERSISTED_RESOURCE_CHECKED, params); + } + } + + private EmpiTransactionContext createEmpiContext(ResourceModifiedMessage theMsg) { + TransactionLogMessages transactionLogMessages = TransactionLogMessages.createFromTransactionGuid(theMsg.getParentTransactionGuid()); + EmpiTransactionContext.OperationType empiOperation; + switch (theMsg.getOperationType()) { + case CREATE: + empiOperation = EmpiTransactionContext.OperationType.CREATE; + break; + case UPDATE: + empiOperation = EmpiTransactionContext.OperationType.UPDATE; + break; + case DELETE: + default: + ourLog.trace("Not creating an EmpiTransactionContext for {}", theMsg.getOperationType()); + throw new InvalidRequestException("We can't handle non-update/create operations in EMPI"); + } + return new EmpiTransactionContext(transactionLogMessages, empiOperation); + } + + private void validateResourceType(String theResourceType) { + if (!EmpiUtil.supportedTargetType(theResourceType)) { + throw new IllegalStateException("Unsupported resource type submitted to EMPI matching queue: " + theResourceType); + } + } + + private void handleCreatePatientOrPractitioner(ResourceModifiedMessage theMsg, EmpiTransactionContext theEmpiTransactionContext) { + myEmpiMatchLinkSvc.updateEmpiLinksForEmpiTarget(getResourceFromPayload(theMsg), theEmpiTransactionContext); + } + + private IAnyResource getResourceFromPayload(ResourceModifiedMessage theMsg) { + return (IAnyResource) theMsg.getNewPayload(myFhirContext); + } + + private void handleUpdatePatientOrPractitioner(ResourceModifiedMessage theMsg, EmpiTransactionContext theEmpiTransactionContext) { + myEmpiMatchLinkSvc.updateEmpiLinksForEmpiTarget(getResourceFromPayload(theMsg), theEmpiTransactionContext); + } + + private void log(EmpiTransactionContext theMessages, String theMessage) { + theMessages.addTransactionLogMessage(theMessage); + ourLog.debug(theMessage); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/broker/EmpiQueueConsumerLoader.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/broker/EmpiQueueConsumerLoader.java new file mode 100644 index 00000000000..fc9f4eb005c --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/broker/EmpiQueueConsumerLoader.java @@ -0,0 +1,76 @@ +package ca.uhn.fhir.jpa.empi.broker; + +import ca.uhn.fhir.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.jpa.subscription.channel.api.ChannelConsumerSettings; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelReceiver; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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% + */ + +@Service +public class EmpiQueueConsumerLoader { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + private EmpiMessageHandler myEmpiMessageHandler; + @Autowired + private IChannelFactory myChannelFactory; + @Autowired + private IEmpiSettings myEmpiSettings; + + protected IChannelReceiver myEmpiChannel; + + @PostConstruct + public void startListeningToEmpiChannel() { + if (myEmpiChannel == null) { + ChannelConsumerSettings config = new ChannelConsumerSettings(); + config.setConcurrentConsumers(myEmpiSettings.getConcurrentConsumers()); + myEmpiChannel = myChannelFactory.getOrCreateReceiver(IEmpiSettings.EMPI_CHANNEL_NAME, ResourceModifiedJsonMessage.class, config); + } + + if (myEmpiChannel != null) { + myEmpiChannel.subscribe(myEmpiMessageHandler); + ourLog.info("EMPI Matching Consumer subscribed to Matching Channel {} with name {}", myEmpiChannel.getClass().getName(), myEmpiChannel.getName()); + } + } + + @SuppressWarnings("unused") + @PreDestroy + public void stop() { + if (myEmpiChannel != null) { + myEmpiChannel.unsubscribe(myEmpiMessageHandler); + } + } + + @VisibleForTesting + public IChannelReceiver getEmpiChannelForUnitTest() { + return myEmpiChannel; + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java new file mode 100644 index 00000000000..548009e27bb --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java @@ -0,0 +1,203 @@ +package ca.uhn.fhir.jpa.empi.config; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.IEmpiLinkQuerySvc; +import ca.uhn.fhir.empi.api.IEmpiLinkSvc; +import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc; +import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc; +import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc; +import ca.uhn.fhir.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.empi.provider.EmpiProviderLoader; +import ca.uhn.fhir.empi.rules.config.EmpiRuleValidator; +import ca.uhn.fhir.empi.rules.svc.EmpiResourceComparatorSvc; +import ca.uhn.fhir.empi.util.EIDHelper; +import ca.uhn.fhir.empi.util.PersonHelper; +import ca.uhn.fhir.jpa.empi.broker.EmpiMessageHandler; +import ca.uhn.fhir.jpa.empi.broker.EmpiQueueConsumerLoader; +import ca.uhn.fhir.jpa.empi.interceptor.EmpiStorageInterceptor; +import ca.uhn.fhir.jpa.empi.interceptor.IEmpiStorageInterceptor; +import ca.uhn.fhir.jpa.empi.svc.EmpiCandidateSearchSvc; +import ca.uhn.fhir.jpa.empi.svc.EmpiEidUpdateService; +import ca.uhn.fhir.jpa.empi.svc.EmpiLinkQuerySvcImpl; +import ca.uhn.fhir.jpa.empi.svc.EmpiLinkSvcImpl; +import ca.uhn.fhir.jpa.empi.svc.EmpiLinkUpdaterSvcImpl; +import ca.uhn.fhir.jpa.empi.svc.EmpiMatchFinderSvcImpl; +import ca.uhn.fhir.jpa.empi.svc.EmpiMatchLinkSvc; +import ca.uhn.fhir.jpa.empi.svc.EmpiPersonFindingSvc; +import ca.uhn.fhir.jpa.empi.svc.EmpiPersonMergerSvcImpl; +import ca.uhn.fhir.jpa.empi.svc.EmpiResourceDaoSvc; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; + +import javax.annotation.PostConstruct; + +@Configuration +public class EmpiConsumerConfig { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + IEmpiSettings myEmpiProperties; + @Autowired + EmpiRuleValidator myEmpiRuleValidator; + @Autowired + EmpiProviderLoader myEmpiProviderLoader; + @Autowired + EmpiSubscriptionLoader myEmpiSubscriptionLoader; + @Autowired + EmpiSearchParameterLoader myEmpiSearchParameterLoader; + + @Bean + IEmpiStorageInterceptor empiStorageInterceptor() { + return new EmpiStorageInterceptor(); + } + + @Bean + EmpiQueueConsumerLoader empiQueueConsumerLoader() { + return new EmpiQueueConsumerLoader(); + } + + @Bean + EmpiMessageHandler empiMessageHandler() { + return new EmpiMessageHandler(); + } + + @Bean + EmpiMatchLinkSvc empiMatchLinkSvc() { + return new EmpiMatchLinkSvc(); + } + + @Bean + EmpiEidUpdateService eidUpdateService() { + return new EmpiEidUpdateService(); + } + + @Bean + EmpiResourceDaoSvc empiResourceDaoSvc() { + return new EmpiResourceDaoSvc(); + } + + @Bean + IEmpiLinkSvc empiLinkSvc() { + return new EmpiLinkSvcImpl(); + } + + @Bean + PersonHelper personHelper(FhirContext theFhirContext) { + return new PersonHelper(theFhirContext); + } + + @Bean + EmpiSubscriptionLoader empiSubscriptionLoader() { + return new EmpiSubscriptionLoader(); + } + + @Bean + EmpiSearchParameterLoader empiSearchParameterLoader() { + return new EmpiSearchParameterLoader(); + } + + @Bean + EmpiPersonFindingSvc empiPersonFindingSvc() { + return new EmpiPersonFindingSvc(); + } + + @Bean + EmpiProviderLoader empiProviderLoader() { + return new EmpiProviderLoader(); + } + + @Bean + EmpiRuleValidator empiRuleValidator() { + return new EmpiRuleValidator(); + } + + @Bean + IEmpiMatchFinderSvc empiMatchFinderSvc() { + return new EmpiMatchFinderSvcImpl(); + } + + @Bean + IEmpiPersonMergerSvc empiPersonMergerSvc() { + return new EmpiPersonMergerSvcImpl(); + } + + + @Bean + IEmpiLinkQuerySvc empiLinkQuerySvc() { + return new EmpiLinkQuerySvcImpl(); + } + + @Bean + EmpiCandidateSearchSvc empiCandidateSearchSvc() { + return new EmpiCandidateSearchSvc(); + } + + @Bean + EmpiResourceComparatorSvc empiResourceComparatorSvc(FhirContext theFhirContext, IEmpiSettings theEmpiConfig) { + return new EmpiResourceComparatorSvc(theFhirContext, theEmpiConfig); + } + + @Bean + EIDHelper eidHelper(FhirContext theFhirContext, IEmpiSettings theEmpiConfig) { + return new EIDHelper(theFhirContext, theEmpiConfig); + } + + @Bean + IEmpiLinkUpdaterSvc manualLinkUpdaterSvc() { + return new EmpiLinkUpdaterSvcImpl(); + } + + @PostConstruct + public void registerInterceptorAndProvider() { + if (!myEmpiProperties.isEnabled()) { + return; + } + + myEmpiRuleValidator.validate(myEmpiProperties.getEmpiRules()); + } + + @EventListener(classes = {ContextRefreshedEvent.class}) + // This @Order is here to ensure that MatchingQueueSubscriberLoader has initialized before we initialize this. + // Otherwise the EMPI subscriptions won't get loaded into the SubscriptionRegistry + @Order + public void updateSubscriptions() { + if (!myEmpiProperties.isEnabled()) { + return; + } + + myEmpiProviderLoader.loadProvider(); + ourLog.info("EMPI provider registered"); + + myEmpiSubscriptionLoader.daoUpdateEmpiSubscriptions(); + ourLog.info("EMPI subscriptions updated"); + + myEmpiSearchParameterLoader.daoUpdateEmpiSearchParameters(); + ourLog.info("EMPI search parameters updated"); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSearchParameterLoader.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSearchParameterLoader.java new file mode 100644 index 00000000000..55a176329a4 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSearchParameterLoader.java @@ -0,0 +1,84 @@ +package ca.uhn.fhir.jpa.empi.config; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.api.EmpiConstants; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.SearchParameter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EmpiSearchParameterLoader { + public static final String EMPI_PERSON_ASSURANCE_SEARCH_PARAMETER_ID = "person-assurance"; + @Autowired + public FhirContext myFhirContext; + @Autowired + public DaoRegistry myDaoRegistry; + + synchronized public void daoUpdateEmpiSearchParameters() { + IBaseResource personAssurance; + switch (myFhirContext.getVersion().getVersion()) { + case DSTU3: + personAssurance = buildEmpiSearchParameterDstu3(); + break; + case R4: + personAssurance = buildEmpiSearchParameterR4(); + break; + default: + throw new ConfigurationException("EMPI not supported for FHIR version " + myFhirContext.getVersion().getVersion()); + } + + IFhirResourceDao searchParameterDao = myDaoRegistry.getResourceDao("SearchParameter"); + searchParameterDao.update(personAssurance); + } + + private org.hl7.fhir.dstu3.model.SearchParameter buildEmpiSearchParameterDstu3() { + org.hl7.fhir.dstu3.model.SearchParameter retval = new org.hl7.fhir.dstu3.model.SearchParameter(); + retval.setId(EMPI_PERSON_ASSURANCE_SEARCH_PARAMETER_ID); + retval.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE); + retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED); + retval.setCode("assurance"); + retval.addBase("Person"); + retval.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.TOKEN); + retval.setDescription("The assurance level of the link on a Person"); + retval.setExpression("Person.link.assurance"); + return retval; + } + + private SearchParameter buildEmpiSearchParameterR4() { + SearchParameter retval = new SearchParameter(); + retval.setId(EMPI_PERSON_ASSURANCE_SEARCH_PARAMETER_ID); + retval.setStatus(Enumerations.PublicationStatus.ACTIVE); + retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED); + retval.setCode("assurance"); + retval.addBase("Person"); + retval.setType(Enumerations.SearchParamType.TOKEN); + retval.setDescription("The assurance level of the link on a Person"); + retval.setExpression("Person.link.assurance"); + return retval; + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSubmitterConfig.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSubmitterConfig.java new file mode 100644 index 00000000000..f6cb3a1b824 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSubmitterConfig.java @@ -0,0 +1,41 @@ +package ca.uhn.fhir.jpa.empi.config; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.interceptor.EmpiSubmitterInterceptorLoader; +import ca.uhn.fhir.jpa.empi.svc.EmpiSearchParamSvc; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class EmpiSubmitterConfig { + + @Bean + EmpiSubmitterInterceptorLoader empiSubmitterInterceptorLoader() { + return new EmpiSubmitterInterceptorLoader(); + } + + @Bean + EmpiSearchParamSvc empiSearchParamSvc() { + return new EmpiSearchParamSvc(); + } + +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSubscriptionLoader.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSubscriptionLoader.java new file mode 100644 index 00000000000..c5532081d2f --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSubscriptionLoader.java @@ -0,0 +1,113 @@ +package ca.uhn.fhir.jpa.empi.config; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.api.EmpiConstants; +import ca.uhn.fhir.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.subscription.channel.api.ChannelProducerSettings; +import ca.uhn.fhir.jpa.subscription.channel.subscription.IChannelNamer; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Subscription; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EmpiSubscriptionLoader { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + public static final String EMPI_PATIENT_SUBSCRIPTION_ID = "empi-patient"; + public static final String EMPI_PRACTITIONER_SUBSCRIPTION_ID = "empi-practitioner"; + @Autowired + public FhirContext myFhirContext; + @Autowired + public DaoRegistry myDaoRegistry; + @Autowired + public IdHelperService myIdHelperService; + @Autowired + IChannelNamer myChannelNamer; + private IFhirResourceDao mySubscriptionDao; + + synchronized public void daoUpdateEmpiSubscriptions() { + IBaseResource patientSub; + IBaseResource practitionerSub; + switch (myFhirContext.getVersion().getVersion()) { + case DSTU3: + patientSub = buildEmpiSubscriptionDstu3(EMPI_PATIENT_SUBSCRIPTION_ID, "Patient?"); + practitionerSub = buildEmpiSubscriptionDstu3(EMPI_PRACTITIONER_SUBSCRIPTION_ID, "Practitioner?"); + break; + case R4: + patientSub = buildEmpiSubscriptionR4(EMPI_PATIENT_SUBSCRIPTION_ID, "Patient?"); + practitionerSub = buildEmpiSubscriptionR4(EMPI_PRACTITIONER_SUBSCRIPTION_ID, "Practitioner?"); + break; + default: + throw new ConfigurationException("EMPI not supported for FHIR version " + myFhirContext.getVersion().getVersion()); + } + + mySubscriptionDao = myDaoRegistry.getResourceDao("Subscription"); + updateIfNotPresent(patientSub); + updateIfNotPresent(practitionerSub); + } + + private synchronized void updateIfNotPresent(IBaseResource theSubscription) { + try { + mySubscriptionDao.read(theSubscription.getIdElement()); + } catch (ResourceNotFoundException e) { + ourLog.info("Creating subsription " + theSubscription.getIdElement()); + mySubscriptionDao.update(theSubscription); + } + } + + private org.hl7.fhir.dstu3.model.Subscription buildEmpiSubscriptionDstu3(String theId, String theCriteria) { + org.hl7.fhir.dstu3.model.Subscription retval = new org.hl7.fhir.dstu3.model.Subscription(); + retval.setId(theId); + retval.setReason("EMPI"); + retval.setStatus(org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus.REQUESTED); + retval.setCriteria(theCriteria); + retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED); + org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelComponent channel = retval.getChannel(); + channel.setType(org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType.MESSAGE); + channel.setEndpoint("channel:" + myChannelNamer.getChannelName(IEmpiSettings.EMPI_CHANNEL_NAME, new ChannelProducerSettings())); + channel.setPayload("application/json"); + return retval; + } + + private Subscription buildEmpiSubscriptionR4(String theId, String theCriteria) { + Subscription retval = new Subscription(); + retval.setId(theId); + retval.setReason("EMPI"); + retval.setStatus(Subscription.SubscriptionStatus.REQUESTED); + retval.setCriteria(theCriteria); + retval.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED); + Subscription.SubscriptionChannelComponent channel = retval.getChannel(); + channel.setType(Subscription.SubscriptionChannelType.MESSAGE); + channel.setEndpoint("channel:" + myChannelNamer.getChannelName(IEmpiSettings.EMPI_CHANNEL_NAME, new ChannelProducerSettings())); + channel.setPayload("application/json"); + return retval; + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptor.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptor.java new file mode 100644 index 00000000000..517ae015349 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptor.java @@ -0,0 +1,172 @@ +package ca.uhn.fhir.jpa.empi.interceptor; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiConstants; +import ca.uhn.fhir.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.model.CanonicalEID; +import ca.uhn.fhir.empi.util.EIDHelper; +import ca.uhn.fhir.empi.util.EmpiUtil; +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.expunge.ExpungeEverythingService; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +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; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +@Service +public class EmpiStorageInterceptor implements IEmpiStorageInterceptor { + private static final Logger ourLog = LoggerFactory.getLogger(EmpiStorageInterceptor.class); + @Autowired + private ExpungeEverythingService myExpungeEverythingService; + @Autowired + private EmpiLinkDaoSvc myEmpiLinkDaoSvc; + @Autowired + private FhirContext myFhirContext; + @Autowired + private EIDHelper myEIDHelper; + @Autowired + private IEmpiSettings myEmpiSettings; + + @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED) + public void blockManualPersonManipulationOnCreate(IBaseResource theBaseResource, RequestDetails theRequestDetails, ServletRequestDetails theServletRequestDetails) { + + //If running in single EID mode, forbid multiple eids. + if (myEmpiSettings.isPreventMultipleEids()) { + forbidIfHasMultipleEids(theBaseResource); + } + + // TODO EMPI find a better way to identify EMPI calls + if (isInternalRequest(theRequestDetails)) { + return; + } + + forbidIfEmpiManagedTagIsPresent(theBaseResource); + } + + @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED) + public void blockManualPersonManipulationOnUpdate(IBaseResource theOldResource, IBaseResource theNewResource, RequestDetails theRequestDetails, ServletRequestDetails theServletRequestDetails) { + //If running in single EID mode, forbid multiple eids. + if (myEmpiSettings.isPreventMultipleEids()) { + forbidIfHasMultipleEids(theNewResource); + } + + if (isInternalRequest(theRequestDetails)) { + return; + } + forbidIfEmpiManagedTagIsPresent(theOldResource); + forbidModifyingEmpiTag(theNewResource, theOldResource); + if (myEmpiSettings.isPreventEidUpdates()) { + forbidIfModifyingExternalEidOnTarget(theNewResource, theOldResource); + } + } + + @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED) + public void deleteEmpiLinks(RequestDetails theRequest, IBaseResource theResource) { + if (!EmpiUtil.isEmpiResourceType(myFhirContext, theResource)) { + return; + } + myEmpiLinkDaoSvc.deleteWithAnyReferenceTo(theResource); + } + + private void forbidIfModifyingExternalEidOnTarget(IBaseResource theNewResource, IBaseResource theOldResource) { + List newExternalEids = myEIDHelper.getExternalEid(theNewResource); + List oldExternalEids = myEIDHelper.getExternalEid(theOldResource); + if (!myEIDHelper.eidMatchExists(newExternalEids, oldExternalEids)) { + throwBlockEidChange(); + } + } + + private void throwBlockEidChange() { + throw new ForbiddenOperationException("While running with EID updates disabled, EIDs may not be updated on Patient/Practitioner resources"); + } + + /* + * Will throw a forbidden error if a request attempts to add/remove the EMPI tag on a Person. + */ + private void forbidModifyingEmpiTag(IBaseResource theNewResource, IBaseResource theOldResource) { + if (EmpiUtil.isEmpiManaged(theNewResource) != EmpiUtil.isEmpiManaged(theOldResource)) { + throwBlockEmpiManagedTagChange(); + } + } + + private void forbidIfHasMultipleEids(IBaseResource theResource) { + String resourceType = extractResourceType(theResource); + if (resourceType.equalsIgnoreCase("Patient") || resourceType.equalsIgnoreCase("Practitioner")) { + if (myEIDHelper.getExternalEid(theResource).size() > 1) { + throwBlockMultipleEids(); + } + } + } + + /* + * We assume that if we have RequestDetails, then this was an HTTP request and not an internal one. + */ + private boolean isInternalRequest(RequestDetails theRequestDetails) { + return theRequestDetails == null; + } + + + private void forbidIfEmpiManagedTagIsPresent(IBaseResource theResource) { + if (EmpiUtil.isEmpiManaged(theResource)) { + throwModificationBlockedByEmpi(); + } + } + + private void throwBlockEmpiManagedTagChange() { + throw new ForbiddenOperationException("The " + EmpiConstants.CODE_HAPI_EMPI_MANAGED + " tag on a resource may not be changed once created."); + } + + private void throwModificationBlockedByEmpi() { + throw new ForbiddenOperationException("Cannot create or modify Resources that are managed by EMPI."); + } + + private void throwBlockMultipleEids() { + throw new ForbiddenOperationException("While running with multiple EIDs disabled, Patient/Practitioner resources may have at most one EID."); + } + + private String extractResourceType(IBaseResource theResource) { + return myFhirContext.getResourceType(theResource); + } + + @Hook(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING) + public void expungeAllEmpiLinks(AtomicInteger theCounter) { + ourLog.debug("Expunging all EmpiLink records"); + theCounter.addAndGet(myExpungeEverythingService.expungeEverythingByType(EmpiLink.class)); + } + + @Hook(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE) + public void expungeAllMatchedEmpiLinks(AtomicInteger theCounter, IBaseResource theResource) { + ourLog.debug("Expunging EmpiLink records with reference to {}", theResource.getIdElement()); + theCounter.addAndGet(myEmpiLinkDaoSvc.deleteWithAnyReferenceTo(theResource)); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiSubmitterInterceptorLoader.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiSubmitterInterceptorLoader.java new file mode 100644 index 00000000000..db847aa5104 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiSubmitterInterceptorLoader.java @@ -0,0 +1,60 @@ +package ca.uhn.fhir.jpa.empi.interceptor; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionSubmitInterceptorLoader; +import org.hl7.fhir.dstu2.model.Subscription; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.PostConstruct; + +public class EmpiSubmitterInterceptorLoader { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + private IEmpiSettings myEmpiProperties; + @Autowired + DaoConfig myDaoConfig; + @Autowired + private IEmpiStorageInterceptor myIEmpiStorageInterceptor; + @Autowired + private IInterceptorService myInterceptorService; + @Autowired + private SubscriptionSubmitInterceptorLoader mySubscriptionSubmitInterceptorLoader; + + @PostConstruct + public void start() { + if (!myEmpiProperties.isEnabled()) { + return; + } + + myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.MESSAGE); + myInterceptorService.registerInterceptor(myIEmpiStorageInterceptor); + ourLog.info("EMPI interceptor registered"); + // We need to call SubscriptionSubmitInterceptorLoader.start() again in case there were no subscription types the first time it was called. + mySubscriptionSubmitInterceptorLoader.start(); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/IEmpiStorageInterceptor.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/IEmpiStorageInterceptor.java new file mode 100644 index 00000000000..50cf4e54a53 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/IEmpiStorageInterceptor.java @@ -0,0 +1,24 @@ +package ca.uhn.fhir.jpa.empi.interceptor; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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 interface IEmpiStorageInterceptor { +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvc.java new file mode 100644 index 00000000000..0495cd471ff --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvc.java @@ -0,0 +1,175 @@ +package ca.uhn.fhir.jpa.empi.svc; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.empi.rules.json.EmpiFilterSearchParamJson; +import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static ca.uhn.fhir.empi.api.EmpiConstants.ALL_RESOURCE_SEARCH_PARAM_TYPE; + +@Service +public class EmpiCandidateSearchSvc { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + private IEmpiSettings myEmpiConfig; + @Autowired + private EmpiSearchParamSvc myEmpiSearchParamSvc; + @Autowired + private DaoRegistry myDaoRegistry; + @Autowired + private IdHelperService myIdHelperService; + + /** + * Given a target resource, search for all resources that are considered an EMPI match based on defined EMPI rules. + * + * + * @param theResourceType + * @param theResource the target {@link IBaseResource} we are attempting to match. + * + * @return the list of candidate {@link IBaseResource} which could be matches to theResource + */ + public Collection findCandidates(String theResourceType, IAnyResource theResource) { + Map matchedPidsToResources = new HashMap<>(); + + List filterSearchParams = myEmpiConfig.getEmpiRules().getFilterSearchParams(); + + List filterCriteria = buildFilterQuery(filterSearchParams, theResourceType); + + for (EmpiResourceSearchParamJson resourceSearchParam : myEmpiConfig.getEmpiRules().getResourceSearchParams()) { + + if (!isSearchParamForResource(theResourceType, resourceSearchParam)) { + continue; + } + + //to compare it to all known PERSON objects, using the overlapping search parameters that they have. + List valuesFromResourceForSearchParam = myEmpiSearchParamSvc.getValueFromResourceForSearchParam(theResource, resourceSearchParam); + if (valuesFromResourceForSearchParam.isEmpty()) { + continue; + } + + searchForIdsAndAddToMap(theResourceType, matchedPidsToResources, filterCriteria, resourceSearchParam, valuesFromResourceForSearchParam); + } + //Obviously we don't want to consider the freshly added resource as a potential candidate. + //Sometimes, we are running this function on a resource that has not yet been persisted, + //so it may not have an ID yet, precluding the need to remove it. + if (theResource.getIdElement().getIdPart() != null) { + matchedPidsToResources.remove(myIdHelperService.getPidOrNull(theResource)); + } + return matchedPidsToResources.values(); + } + + private boolean isSearchParamForResource(String theResourceType, EmpiResourceSearchParamJson resourceSearchParam) { + String resourceType = resourceSearchParam.getResourceType(); + return resourceType.equals(theResourceType) || resourceType.equalsIgnoreCase(ALL_RESOURCE_SEARCH_PARAM_TYPE); + } + + /* + * Helper method which performs too much work currently. + * 1. Build a full query string for the given filter and resource criteria. + * 2. Convert that URL to a SearchParameterMap. + * 3. Execute a Synchronous search on the DAO using that parameter map. + * 4. Store all results in `theMatchedPidsToResources` + */ + @SuppressWarnings("rawtypes") + private void searchForIdsAndAddToMap(String theResourceType, Map theMatchedPidsToResources, List theFilterCriteria, EmpiResourceSearchParamJson resourceSearchParam, List theValuesFromResourceForSearchParam) { + //1. + String resourceCriteria = buildResourceQueryString(theResourceType, theFilterCriteria, resourceSearchParam, theValuesFromResourceForSearchParam); + ourLog.debug("Searching for {} candidates with {}", theResourceType, resourceCriteria); + + //2. + SearchParameterMap searchParameterMap = myEmpiSearchParamSvc.mapFromCriteria(theResourceType, resourceCriteria); + + searchParameterMap.setLoadSynchronous(true); + + //TODO EMPI this will blow up under large scale i think. + //3. + IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theResourceType); + IBundleProvider search = resourceDao.search(searchParameterMap); + List resources = search.getResources(0, search.size()); + + int initialSize = theMatchedPidsToResources.size(); + + //4. + resources.forEach(resource -> theMatchedPidsToResources.put(myIdHelperService.getPidOrNull(resource), (IAnyResource) resource)); + + int newSize = theMatchedPidsToResources.size(); + + if (ourLog.isDebugEnabled()) { + ourLog.debug("Candidate search added {} {}s", newSize - initialSize, theResourceType); + } + } + + /* + * Given a list of criteria upon which to block, a resource search parameter, and a list of values for that given search parameter, + * build a query url. e.g. + * + * Patient?active=true&name.given=Gary,Grant + */ + @Nonnull + private String buildResourceQueryString(String theResourceType, List theFilterCriteria, EmpiResourceSearchParamJson resourceSearchParam, List theValuesFromResourceForSearchParam) { + List criteria = new ArrayList<>(theFilterCriteria); + criteria.add(buildResourceMatchQuery(resourceSearchParam.getSearchParam(), theValuesFromResourceForSearchParam)); + + return theResourceType + "?" + String.join("&", criteria); + } + + private String buildResourceMatchQuery(String theSearchParamName, List theResourceValues) { + return theSearchParamName + "=" + String.join(",", theResourceValues); + } + + private List buildFilterQuery(List theFilterSearchParams, String theResourceType) { + return Collections.unmodifiableList(theFilterSearchParams.stream() + .filter(spFilterJson -> paramIsOnCorrectType(theResourceType, spFilterJson)) + .map(this::convertToQueryString) + .collect(Collectors.toList())); + } + + private boolean paramIsOnCorrectType(String theResourceType, EmpiFilterSearchParamJson spFilterJson) { + return spFilterJson.getResourceType().equals(theResourceType) || spFilterJson.getResourceType().equalsIgnoreCase(ALL_RESOURCE_SEARCH_PARAM_TYPE); + } + + private String convertToQueryString(EmpiFilterSearchParamJson theSpFilterJson) { + String qualifier = theSpFilterJson.getTokenParamModifierAsString(); + return theSpFilterJson.getSearchParam() + qualifier + "=" + theSpFilterJson.getFixedValue(); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiEidUpdateService.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiEidUpdateService.java new file mode 100644 index 00000000000..3605d75b267 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiEidUpdateService.java @@ -0,0 +1,156 @@ +package ca.uhn.fhir.jpa.empi.svc; + +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiLinkSvc; +import ca.uhn.fhir.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.empi.model.CanonicalEID; +import ca.uhn.fhir.empi.model.EmpiTransactionContext; +import ca.uhn.fhir.empi.util.EIDHelper; +import ca.uhn.fhir.empi.util.PersonHelper; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class EmpiEidUpdateService { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + private EmpiResourceDaoSvc myEmpiResourceDaoSvc; + @Autowired + private IEmpiLinkSvc myEmpiLinkSvc; + @Autowired + private EmpiPersonFindingSvc myEmpiPersonFindingSvc; + @Autowired + private PersonHelper myPersonHelper; + @Autowired + private EIDHelper myEIDHelper; + @Autowired + private EmpiLinkDaoSvc myEmpiLinkDaoSvc; + @Autowired + private IEmpiSettings myEmpiSettings; + + void handleEmpiUpdate(IAnyResource theResource, MatchedPersonCandidate theMatchedPersonCandidate, EmpiTransactionContext theEmpiTransactionContext) { + + EmpiUpdateContext updateContext = new EmpiUpdateContext(theMatchedPersonCandidate, theResource); + + if (updateContext.isRemainsMatchedToSamePerson()) { + myPersonHelper.updatePersonFromUpdatedEmpiTarget(updateContext.getMatchedPerson(), theResource, theEmpiTransactionContext); + if (!updateContext.isIncomingResourceHasAnEid() || updateContext.isHasEidsInCommon()) { + //update to patient that uses internal EIDs only. + myEmpiLinkSvc.updateLink(updateContext.getMatchedPerson(), theResource, theMatchedPersonCandidate.getMatchResult(), EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext); + } else if (!updateContext.isHasEidsInCommon()) { + handleNoEidsInCommon(theResource, theMatchedPersonCandidate, theEmpiTransactionContext, updateContext); + } + } else { + //This is a new linking scenario. we have to break the existing link and link to the new person. For now, we create duplicate. + //updated patient has an EID that matches to a new candidate. Link them, and set the persons possible duplicates + linkToNewPersonAndFlagAsDuplicate(theResource, updateContext.getExistingPerson(), updateContext.getMatchedPerson(), theEmpiTransactionContext); + } + } + + private void handleNoEidsInCommon(IAnyResource theResource, MatchedPersonCandidate theMatchedPersonCandidate, EmpiTransactionContext theEmpiTransactionContext, EmpiUpdateContext theUpdateContext) { + // the user is simply updating their EID. We propagate this change to the Person. + //overwrite. No EIDS in common, but still same person. + if (myEmpiSettings.isPreventMultipleEids()) { + if (myPersonHelper.getLinkCount(theUpdateContext.getMatchedPerson()) <= 1) { // If there is only 0/1 link on the person, we can safely overwrite the EID. + handleExternalEidOverwrite(theUpdateContext.getMatchedPerson(), theResource, theEmpiTransactionContext); + } else { // If the person has multiple patients tied to it, we can't just overwrite the EID, so we split the person. + createNewPersonAndFlagAsDuplicate(theResource, theEmpiTransactionContext, theUpdateContext.getExistingPerson()); + } + } else { + myPersonHelper.handleExternalEidAddition(theUpdateContext.getMatchedPerson(), theResource, theEmpiTransactionContext); + } + myEmpiLinkSvc.updateLink(theUpdateContext.getMatchedPerson(), theResource, theMatchedPersonCandidate.getMatchResult(), EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext); + } + + private void handleExternalEidOverwrite(IAnyResource thePerson, IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext) { + List eidFromResource = myEIDHelper.getExternalEid(theResource); + if (!eidFromResource.isEmpty()) { + myPersonHelper.overwriteExternalEids(thePerson, eidFromResource); + } + } + + private boolean candidateIsSameAsEmpiLinkPerson(EmpiLink theExistingMatchLink, MatchedPersonCandidate thePersonCandidate) { + return theExistingMatchLink.getPersonPid().equals(thePersonCandidate.getCandidatePersonPid().getIdAsLong()); + } + + private void createNewPersonAndFlagAsDuplicate(IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext, IAnyResource theOldPerson) { + log(theEmpiTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs."); + IAnyResource newPerson = myPersonHelper.createPersonFromEmpiTarget(theResource); + myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext); + myEmpiLinkSvc.updateLink(newPerson, theOldPerson, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext); + } + + private void linkToNewPersonAndFlagAsDuplicate(IAnyResource theResource, IAnyResource theOldPerson, IAnyResource theNewPerson, EmpiTransactionContext theEmpiTransactionContext) { + log(theEmpiTransactionContext, "Changing a match link!"); + myEmpiLinkSvc.deleteLink(theOldPerson, theResource, theEmpiTransactionContext); + myEmpiLinkSvc.updateLink(theNewPerson, theResource, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext); + log(theEmpiTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs."); + myEmpiLinkSvc.updateLink(theNewPerson, theOldPerson, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext); + } + + private void log(EmpiTransactionContext theEmpiTransactionContext, String theMessage) { + theEmpiTransactionContext.addTransactionLogMessage(theMessage); + ourLog.debug(theMessage); + } + + /** + * Data class to hold context surrounding an update operation for an EMPI target. + */ + class EmpiUpdateContext { + private final boolean myHasEidsInCommon; + private final boolean myIncomingResourceHasAnEid; + private IAnyResource myExistingPerson; + private boolean myRemainsMatchedToSamePerson; + + public IAnyResource getMatchedPerson() { + return myMatchedPerson; + } + + private final IAnyResource myMatchedPerson; + + EmpiUpdateContext(MatchedPersonCandidate theMatchedPersonCandidate, IAnyResource theResource) { + myMatchedPerson = myEmpiPersonFindingSvc.getPersonFromMatchedPersonCandidate(theMatchedPersonCandidate); + + myHasEidsInCommon = myEIDHelper.hasEidOverlap(myMatchedPerson, theResource); + myIncomingResourceHasAnEid = !myEIDHelper.getExternalEid(theResource).isEmpty(); + + Optional theExistingMatchLink = myEmpiLinkDaoSvc.getMatchedLinkForTarget(theResource); + myExistingPerson = null; + + if (theExistingMatchLink.isPresent()) { + Long existingPersonPid = theExistingMatchLink.get().getPersonPid(); + myExistingPerson = myEmpiResourceDaoSvc.readPersonByPid(new ResourcePersistentId(existingPersonPid)); + myRemainsMatchedToSamePerson = candidateIsSameAsEmpiLinkPerson(theExistingMatchLink.get(), theMatchedPersonCandidate); + } else { + myRemainsMatchedToSamePerson = false; + } + } + + public boolean isHasEidsInCommon() { + return myHasEidsInCommon; + } + + public boolean isIncomingResourceHasAnEid() { + return myIncomingResourceHasAnEid; + } + + public IAnyResource getExistingPerson() { + return myExistingPerson; + } + + public boolean isRemainsMatchedToSamePerson() { + return myRemainsMatchedToSamePerson; + } + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkQuerySvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkQuerySvcImpl.java new file mode 100644 index 00000000000..614700faf75 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkQuerySvcImpl.java @@ -0,0 +1,106 @@ +package ca.uhn.fhir.jpa.empi.svc; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc; +import ca.uhn.fhir.empi.model.EmpiTransactionContext; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.util.ParametersUtil; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseParameters; +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.data.domain.Example; + +import java.util.List; +import java.util.stream.Collectors; + +public class EmpiLinkQuerySvcImpl implements IEmpiLinkQuerySvc { + private static final Logger ourLog = LoggerFactory.getLogger(EmpiLinkQuerySvcImpl.class); + + @Autowired + FhirContext myFhirContext; + @Autowired + IdHelperService myIdHelperService; + @Autowired + EmpiLinkDaoSvc myEmpiLinkDaoSvc; + + @Override + public IBaseParameters queryLinks(IIdType thePersonId, IIdType theTargetId, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiContext) { + Example exampleLink = exampleLinkFromParameters(thePersonId, theTargetId, theMatchResult, theLinkSource); + List empiLinks = myEmpiLinkDaoSvc.findEmpiLinkByExample(exampleLink).stream() + .filter(empiLink -> empiLink.getMatchResult() != EmpiMatchResultEnum.POSSIBLE_DUPLICATE) + .collect(Collectors.toList()); + // TODO RC1 KHS page results + return parametersFromEmpiLinks(empiLinks, true); + } + + @Override + public IBaseParameters getPossibleDuplicates(EmpiTransactionContext theEmpiContext) { + Example exampleLink = exampleLinkFromParameters(null, null, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, null); + List empiLinks = myEmpiLinkDaoSvc.findEmpiLinkByExample(exampleLink); + // TODO RC1 page results + return parametersFromEmpiLinks(empiLinks, false); + } + + private IBaseParameters parametersFromEmpiLinks(List theEmpiLinks, boolean includeResultAndSource) { + IBaseParameters retval = ParametersUtil.newInstance(myFhirContext); + + for (EmpiLink empiLink : theEmpiLinks) { + IBase resultPart = ParametersUtil.addParameterToParameters(myFhirContext, retval, "link"); + String personId = myIdHelperService.resourceIdFromPidOrThrowException(empiLink.getPersonPid()).toVersionless().getValue(); + ParametersUtil.addPartString(myFhirContext, resultPart, "personId", personId); + + String targetId = myIdHelperService.resourceIdFromPidOrThrowException(empiLink.getTargetPid()).toVersionless().getValue(); + ParametersUtil.addPartString(myFhirContext, resultPart, "targetId", targetId); + + if (includeResultAndSource) { + ParametersUtil.addPartString(myFhirContext, resultPart, "matchResult", empiLink.getMatchResult().name()); + ParametersUtil.addPartString(myFhirContext, resultPart, "linkSource", empiLink.getLinkSource().name()); + } + } + return retval; + } + + private Example exampleLinkFromParameters(IIdType thePersonId, IIdType theTargetId, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource) { + EmpiLink empiLink = new EmpiLink(); + if (thePersonId != null) { + empiLink.setPersonPid(myIdHelperService.getPidOrThrowException(thePersonId)); + } + if (theTargetId != null) { + empiLink.setTargetPid(myIdHelperService.getPidOrThrowException(theTargetId)); + } + if (theMatchResult != null) { + empiLink.setMatchResult(theMatchResult); + } + if (theLinkSource != null) { + empiLink.setLinkSource(theLinkSource); + } + return Example.of(empiLink); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcImpl.java new file mode 100644 index 00000000000..a83f0a998c5 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcImpl.java @@ -0,0 +1,174 @@ +package ca.uhn.fhir.jpa.empi.svc; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiLinkSvc; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel; +import ca.uhn.fhir.empi.model.EmpiTransactionContext; +import ca.uhn.fhir.empi.util.AssuranceLevelUtil; +import ca.uhn.fhir.empi.util.PersonHelper; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseBackboneElement; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * This class is in charge of managing EmpiLinks between Persons and target resources + */ +@Service +public class EmpiLinkSvcImpl implements IEmpiLinkSvc { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + private EmpiResourceDaoSvc myEmpiResourceDaoSvc; + @Autowired + private EmpiLinkDaoSvc myEmpiLinkDaoSvc; + @Autowired + private PersonHelper myPersonHelper; + @Autowired + private IdHelperService myIdHelperService; + + @Override + @Transactional + public void updateLink(IAnyResource thePerson, IAnyResource theResource, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiTransactionContext) { + IIdType resourceId = theResource.getIdElement().toUnqualifiedVersionless(); + + validateRequestIsLegal(thePerson, theResource, theMatchResult, theLinkSource); + switch (theMatchResult) { + case MATCH: + //deleteCurrentMatch(theResource); + myPersonHelper.addOrUpdateLink(thePerson, resourceId, AssuranceLevelUtil.getAssuranceLevel(theMatchResult, theLinkSource), theEmpiTransactionContext); + myEmpiResourceDaoSvc.updatePerson(thePerson); + break; + case POSSIBLE_MATCH: + myPersonHelper.addOrUpdateLink(thePerson, resourceId, AssuranceLevelUtil.getAssuranceLevel(theMatchResult, theLinkSource), theEmpiTransactionContext); + break; + case NO_MATCH: + myPersonHelper.removeLink(thePerson, resourceId, theEmpiTransactionContext); + break; + case POSSIBLE_DUPLICATE: + break; + } + myEmpiResourceDaoSvc.updatePerson(thePerson); + createOrUpdateLinkEntity(thePerson, theResource, theMatchResult, theLinkSource, theEmpiTransactionContext); + + } + + @Override + @Transactional + public void syncEmpiLinksToPersonLinks(IAnyResource thePersonResource, EmpiTransactionContext theEmpiTransactionContext) { + int origLinkCount = myPersonHelper.getLinkCount(thePersonResource); + + List empiLinks = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(thePersonResource); + + List newLinks = empiLinks.stream() + .filter(link -> link.isMatch() || link.isPossibleMatch()) + .map(this::personLinkFromEmpiLink) + .collect(Collectors.toList()); + myPersonHelper.setLinks(thePersonResource, newLinks); + myEmpiResourceDaoSvc.updatePerson(thePersonResource); + if (newLinks.size() > origLinkCount) { + log(theEmpiTransactionContext, thePersonResource.getIdElement().toVersionless() + " links increased from " + origLinkCount + " to " + newLinks.size()); + } else if (newLinks.size() < origLinkCount) { + log(theEmpiTransactionContext, thePersonResource.getIdElement().toVersionless() + " links decreased from " + origLinkCount + " to " + newLinks.size()); + } + } + + @Override + public void deleteLink(IAnyResource theExistingPerson, IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext) { + myPersonHelper.removeLink(theExistingPerson, theResource.getIdElement(), theEmpiTransactionContext); + Optional oEmpiLink = getEmpiLinkForPersonTargetPair(theExistingPerson, theResource); + if (oEmpiLink.isPresent()) { + EmpiLink empiLink = oEmpiLink.get(); + log(theEmpiTransactionContext, "Deleting EmpiLink [" + theExistingPerson.getIdElement().toVersionless() + " -> " + theResource.getIdElement().toVersionless() + "] with result: " + empiLink.getMatchResult()); + myEmpiLinkDaoSvc.deleteLink(empiLink); + } + } + + private IBaseBackboneElement personLinkFromEmpiLink(EmpiLink empiLink) { + IIdType resourceId = myIdHelperService.resourceIdFromPidOrThrowException(empiLink.getTargetPid()); + CanonicalIdentityAssuranceLevel assuranceLevel = AssuranceLevelUtil.getAssuranceLevel(empiLink.getMatchResult(), empiLink.getLinkSource()); + return myPersonHelper.newPersonLink(resourceId, assuranceLevel); + } + + /** + * Helper function which runs various business rules about what types of requests are allowed. + */ + private void validateRequestIsLegal(IAnyResource thePerson, IAnyResource theResource, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource) { + Optional oExistingLink = getEmpiLinkForPersonTargetPair(thePerson, theResource); + if (oExistingLink.isPresent() && systemIsAttemptingToModifyManualLink(theLinkSource, oExistingLink.get())) { + throw new InternalErrorException("EMPI system is not allowed to modify links on manually created links"); + } + + if (systemIsAttemptingToAddNoMatch(theLinkSource, theMatchResult)) { + throw new InternalErrorException("EMPI system is not allowed to automatically NO_MATCH a resource"); + } + } + + /** + * Helper function which detects when the EMPI system is attempting to add a NO_MATCH link, which is not allowed. + */ + private boolean systemIsAttemptingToAddNoMatch(EmpiLinkSourceEnum theLinkSource, EmpiMatchResultEnum theMatchResult) { + return theLinkSource == EmpiLinkSourceEnum.AUTO && theMatchResult == EmpiMatchResultEnum.NO_MATCH; + } + + /** + * Helper function to let us catch when System EMPI rules are attempting to override a manually defined link. + */ + private boolean systemIsAttemptingToModifyManualLink(EmpiLinkSourceEnum theIncomingSource, EmpiLink theExistingSource) { + return theIncomingSource == EmpiLinkSourceEnum.AUTO && theExistingSource.isManual(); + } + + private Optional getEmpiLinkForPersonTargetPair(IAnyResource thePerson, IAnyResource theCandidate) { + if (thePerson.getIdElement().getIdPart() == null || theCandidate.getIdElement().getIdPart() == null) { + return Optional.empty(); + } else { + return myEmpiLinkDaoSvc.getLinkByPersonPidAndTargetPid( + myIdHelperService.getPidOrNull(thePerson), + myIdHelperService.getPidOrNull(theCandidate) + ); + } + } + + private void createOrUpdateLinkEntity(IBaseResource thePerson, IBaseResource theResource, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiTransactionContext) { + myEmpiLinkDaoSvc.createOrUpdateLinkEntity(thePerson, theResource, theMatchResult, theLinkSource, theEmpiTransactionContext); + } + + private void log(EmpiTransactionContext theEmpiTransactionContext, String theMessage) { + theEmpiTransactionContext.addTransactionLogMessage(theMessage); + ourLog.debug(theMessage); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkUpdaterSvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkUpdaterSvcImpl.java new file mode 100644 index 00000000000..86daace4b89 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkUpdaterSvcImpl.java @@ -0,0 +1,100 @@ +package ca.uhn.fhir.jpa.empi.svc; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiConstants; +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiLinkSvc; +import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.empi.model.EmpiTransactionContext; +import ca.uhn.fhir.empi.util.EmpiUtil; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +public class EmpiLinkUpdaterSvcImpl implements IEmpiLinkUpdaterSvc { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + FhirContext myFhirContext; + @Autowired + IdHelperService myIdHelperService; + @Autowired + EmpiLinkDaoSvc myEmpiLinkDaoSvc; + @Autowired + IEmpiLinkSvc myEmpiLinkSvc; + + @Transactional + @Override + public IAnyResource updateLink(IAnyResource thePerson, IAnyResource theTarget, EmpiMatchResultEnum theMatchResult, EmpiTransactionContext theEmpiContext) { + if (theMatchResult != EmpiMatchResultEnum.NO_MATCH && + theMatchResult != EmpiMatchResultEnum.MATCH) { + throw new InvalidRequestException("Match Result may only be set to " + EmpiMatchResultEnum.NO_MATCH + " or " + EmpiMatchResultEnum.MATCH); + } + + String personType = myFhirContext.getResourceType(thePerson); + if (!"Person".equals(personType)) { + throw new InvalidRequestException("First argument to updateLink must be a Person. Was " + personType); + } + String targetType = myFhirContext.getResourceType(theTarget); + if (!EmpiUtil.supportedTargetType(targetType)) { + throw new InvalidRequestException("Second argument to updateLink must be a Patient or Practitioner. Was " + targetType); + } + + if (!EmpiUtil.isEmpiManaged(thePerson)) { + throw new InvalidRequestException("Only EMPI Managed Person resources may be updated via this operation. The Person resource provided is not tagged as managed by hapi-empi"); + } + + if (!EmpiUtil.isEmpiAccessible(theTarget)) { + throw new InvalidRequestException("The target is marked with the " + EmpiConstants.CODE_NO_EMPI_MANAGED + " tag which means it may not be EMPI linked."); + } + + Long personId = myIdHelperService.getPidOrThrowException(thePerson); + Long targetId = myIdHelperService.getPidOrThrowException(theTarget); + + Optional oEmpiLink = myEmpiLinkDaoSvc.getLinkByPersonPidAndTargetPid(personId, targetId); + if (!oEmpiLink.isPresent()) { + throw new InvalidRequestException("No link exists between " + thePerson.getIdElement().toVersionless() + " and " + theTarget.getIdElement().toVersionless()); + } + EmpiLink empiLink = oEmpiLink.get(); + if (empiLink.getMatchResult() == theMatchResult) { + ourLog.warn("EMPI Link for " + thePerson.getIdElement().toVersionless() + ", " + theTarget.getIdElement().toVersionless() + " already has value " + theMatchResult + ". Nothing to do."); + return thePerson; + } + + ourLog.info("Manually updating EMPI Link for " + thePerson.getIdElement().toVersionless() + ", " + theTarget.getIdElement().toVersionless() + " from " + empiLink.getMatchResult() + " to " + theMatchResult + "."); + empiLink.setMatchResult(theMatchResult); + empiLink.setLinkSource(EmpiLinkSourceEnum.MANUAL); + myEmpiLinkDaoSvc.save(empiLink); + myEmpiLinkSvc.syncEmpiLinksToPersonLinks(thePerson, theEmpiContext); + return thePerson; + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchFinderSvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchFinderSvcImpl.java new file mode 100644 index 00000000000..fa27cdaa32b --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchFinderSvcImpl.java @@ -0,0 +1,61 @@ +package ca.uhn.fhir.jpa.empi.svc; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.IEmpiMatchFinderSvc; +import ca.uhn.fhir.empi.api.MatchedTarget; +import ca.uhn.fhir.empi.rules.svc.EmpiResourceComparatorSvc; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Nonnull; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class EmpiMatchFinderSvcImpl implements IEmpiMatchFinderSvc { + @Autowired + private EmpiCandidateSearchSvc myEmpiCandidateSearchSvc; + @Autowired + private EmpiResourceComparatorSvc myEmpiResourceComparatorSvc; + + @Override + @Nonnull + public List getMatchedTargets(String theResourceType, IAnyResource theResource) { + Collection targetCandidates = myEmpiCandidateSearchSvc.findCandidates(theResourceType, theResource); + + return targetCandidates.stream() + .map(candidate -> new MatchedTarget(candidate, myEmpiResourceComparatorSvc.getMatchResult(theResource, candidate))) + .collect(Collectors.toList()); + } + + @Override + @Nonnull + public List findMatches(String theResourceType, IAnyResource theResource) { + List targetCandidates = getMatchedTargets(theResourceType, theResource); + return targetCandidates.stream() + .filter(candidate -> candidate.isMatch()) + .map(MatchedTarget::getTarget) + .collect(Collectors.toList()); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvc.java new file mode 100644 index 00000000000..1f8d21ece2f --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvc.java @@ -0,0 +1,148 @@ +package ca.uhn.fhir.jpa.empi.svc; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiLinkSvc; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.empi.model.EmpiTransactionContext; +import ca.uhn.fhir.empi.util.EmpiUtil; +import ca.uhn.fhir.empi.util.PersonHelper; +import ca.uhn.fhir.rest.server.TransactionLogMessages; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * EmpiMatchLinkSvc is the entrypoint for HAPI's EMPI system. An incoming resource can call + * updateEmpiLinksForEmpiTarget and the underlying EMPI system will take care of matching it to a person, or creating a + * new Person if a suitable one was not found. + */ +@Service +public class EmpiMatchLinkSvc { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + private IEmpiLinkSvc myEmpiLinkSvc; + @Autowired + private EmpiPersonFindingSvc myEmpiPersonFindingSvc; + @Autowired + private PersonHelper myPersonHelper; + @Autowired + private EmpiEidUpdateService myEidUpdateService; + + /** + * Given an Empi Target (consisting of either a Patient or a Practitioner), find a suitable Person candidate for them, + * or create one if one does not exist. Performs matching based on rules defined in empi-rules.json. + * Does nothing if resource is determined to be not managed by EMPI. + * + * @param theResource the incoming EMPI target, which is either a Patient or Practitioner. + * @param theEmpiTransactionContext + * @return an {@link TransactionLogMessages} which contains all informational messages related to EMPI processing of this resource. + */ + public EmpiTransactionContext updateEmpiLinksForEmpiTarget(IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext) { + if (EmpiUtil.isEmpiAccessible(theResource)) { + return doEmpiUpdate(theResource, theEmpiTransactionContext); + } else { + return null; + } + } + + private EmpiTransactionContext doEmpiUpdate(IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext) { + List personCandidates = myEmpiPersonFindingSvc.findPersonCandidates(theResource); + if (personCandidates.isEmpty()) { + handleEmpiWithNoCandidates(theResource, theEmpiTransactionContext); + } else if (personCandidates.size() == 1) { + handleEmpiWithSingleCandidate(theResource, personCandidates, theEmpiTransactionContext); + } else { + handleEmpiWithMultipleCandidates(theResource, personCandidates, theEmpiTransactionContext); + } + return theEmpiTransactionContext; + } + + private void handleEmpiWithMultipleCandidates(IAnyResource theResource, List thePersonCandidates, EmpiTransactionContext theEmpiTransactionContext) { + Long samplePersonPid = thePersonCandidates.get(0).getCandidatePersonPid().getIdAsLong(); + boolean allSamePerson = thePersonCandidates.stream() + .allMatch(candidate -> candidate.getCandidatePersonPid().getIdAsLong().equals(samplePersonPid)); + + if (allSamePerson) { + log(theEmpiTransactionContext, "EMPI received multiple match candidates, but they are all linked to the same person."); + handleEmpiWithSingleCandidate(theResource, thePersonCandidates, theEmpiTransactionContext); + } else { + log(theEmpiTransactionContext, "EMPI received multiple match candidates, that were linked to different Persons. Setting POSSIBLE_DUPLICATES and POSSIBLE_MATCHES."); + //Set them all as POSSIBLE_MATCH + List persons = thePersonCandidates.stream().map((MatchedPersonCandidate matchedPersonCandidate) -> myEmpiPersonFindingSvc.getPersonFromMatchedPersonCandidate(matchedPersonCandidate)).collect(Collectors.toList()); + persons.forEach(person -> { + myEmpiLinkSvc.updateLink(person, theResource, EmpiMatchResultEnum.POSSIBLE_MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext); + }); + + //Set all Persons as POSSIBLE_DUPLICATE of the first person. + IAnyResource samplePerson = persons.get(0); + persons.subList(1, persons.size()).stream() + .forEach(possibleDuplicatePerson -> { + myEmpiLinkSvc.updateLink(samplePerson, possibleDuplicatePerson, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext); + }); + } + } + + private void handleEmpiWithNoCandidates(IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext) { + log(theEmpiTransactionContext, "There were no matched candidates for EMPI, creating a new Person."); + IAnyResource newPerson = myPersonHelper.createPersonFromEmpiTarget(theResource); + myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext); + } + + private void handleEmpiCreate(IAnyResource theResource, MatchedPersonCandidate thePersonCandidate, EmpiTransactionContext theEmpiTransactionContext) { + log(theEmpiTransactionContext, "EMPI has narrowed down to one candidate for matching."); + IAnyResource person = myEmpiPersonFindingSvc.getPersonFromMatchedPersonCandidate(thePersonCandidate); + if (myPersonHelper.isPotentialDuplicate(person, theResource)) { + log(theEmpiTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs."); + IAnyResource newPerson = myPersonHelper.createPersonFromEmpiTarget(theResource); + myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext); + myEmpiLinkSvc.updateLink(newPerson, person, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext); + } else { + if (thePersonCandidate.isMatch()) { + myPersonHelper.handleExternalEidAddition(person, theResource, theEmpiTransactionContext); + myPersonHelper.updatePersonFromNewlyCreatedEmpiTarget(person, theResource, theEmpiTransactionContext); + } + myEmpiLinkSvc.updateLink(person, theResource, thePersonCandidate.getMatchResult(), EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext); + } + } + + private void handleEmpiWithSingleCandidate(IAnyResource theResource, List thePersonCandidates, EmpiTransactionContext theEmpiTransactionContext) { + log(theEmpiTransactionContext, "EMPI has narrowed down to one candidate for matching."); + MatchedPersonCandidate matchedPersonCandidate = thePersonCandidates.get(0); + if (theEmpiTransactionContext.getRestOperation().equals(EmpiTransactionContext.OperationType.UPDATE)) { + myEidUpdateService.handleEmpiUpdate(theResource, matchedPersonCandidate, theEmpiTransactionContext); + } else { + handleEmpiCreate(theResource, matchedPersonCandidate, theEmpiTransactionContext); + } + } + + private void log(EmpiTransactionContext theEmpiTransactionContext, String theMessage) { + theEmpiTransactionContext.addTransactionLogMessage(theMessage); + ourLog.debug(theMessage); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonFindingSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonFindingSvc.java new file mode 100644 index 00000000000..14882e3807a --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonFindingSvc.java @@ -0,0 +1,182 @@ +package ca.uhn.fhir.jpa.empi.svc; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc; +import ca.uhn.fhir.empi.api.MatchedTarget; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.empi.model.CanonicalEID; +import ca.uhn.fhir.empi.util.EIDHelper; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +public class EmpiPersonFindingSvc { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + private FhirContext myFhirContext; + @Autowired + IdHelperService myIdHelperService; + @Autowired + private EmpiLinkDaoSvc myEmpiLinkDaoSvc; + @Autowired + private EmpiResourceDaoSvc myEmpiResourceDaoSvc; + @Autowired + private IEmpiMatchFinderSvc myEmpiMatchFinderSvc; + @Autowired + private EIDHelper myEIDHelper; + + /** + * Given an incoming IBaseResource, limited to Patient/Practitioner, return a list of {@link MatchedPersonCandidate} + * indicating possible candidates for a matching Person. Uses several separate methods for finding candidates: + *

+ * 0. First, check the incoming Resource for an EID. If it is present, and we can find a Person with this EID, it automatically matches. + * 1. First, check link table for any entries where this baseresource is the target of a person. If found, return. + * 2. If none are found, attempt to find Person Resources which link to this theResource. + * 3. If none are found, attempt to find Persons similar to our incoming resource based on the EMPI rules and similarity metrics. + * 4. If none are found, attempt to find Persons that are linked to Patients/Practitioners that are similar to our incoming resource based on the EMPI rules and + * similarity metrics. + * + * @param theResource the {@link IBaseResource} we are attempting to find matching candidate Persons for. + * @return A list of {@link MatchedPersonCandidate} indicating all potential Person matches. + */ + public List findPersonCandidates(IAnyResource theResource) { + List matchedPersonCandidates = attemptToFindPersonCandidateFromIncomingEID(theResource); + if (matchedPersonCandidates.isEmpty()) { + matchedPersonCandidates = attemptToFindPersonCandidateFromEmpiLinkTable(theResource); + } + if (matchedPersonCandidates.isEmpty()) { + //OK, so we have not found any links in the EmpiLink table with us as a target. Next, let's find possible Patient/Practitioner + //matches by following EMPI rules. + + matchedPersonCandidates = attemptToFindPersonCandidateFromSimilarTargetResource(theResource); + } + return matchedPersonCandidates; + } + + private List attemptToFindPersonCandidateFromIncomingEID(IAnyResource theBaseResource) { + List retval = new ArrayList<>(); + + List eidFromResource = myEIDHelper.getExternalEid(theBaseResource); + if (!eidFromResource.isEmpty()) { + for (CanonicalEID eid : eidFromResource) { + IBaseResource foundPerson = myEmpiResourceDaoSvc.searchPersonByEid(eid.getValue()); + if (foundPerson != null) { + Long pidOrNull = myIdHelperService.getPidOrNull(foundPerson); + MatchedPersonCandidate mpc = new MatchedPersonCandidate(new ResourcePersistentId(pidOrNull), EmpiMatchResultEnum.MATCH); + ourLog.debug("Matched {} by EID {}", foundPerson.getIdElement(), eid); + retval.add(mpc); + } + } + } + return retval; + } + + /** + * Attempt to find a currently matching Person, based on the presence of an {@link EmpiLink} entity. + * + * @param theBaseResource the {@link IAnyResource} that we want to find candidate Persons for. + * @return an Optional list of {@link MatchedPersonCandidate} indicating matches. + */ + private List attemptToFindPersonCandidateFromEmpiLinkTable(IAnyResource theBaseResource) { + List retval = new ArrayList<>(); + + Long targetPid = myIdHelperService.getPidOrNull(theBaseResource); + if (targetPid != null) { + Optional oLink = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(targetPid); + if (oLink.isPresent()) { + ResourcePersistentId personPid = new ResourcePersistentId(oLink.get().getPersonPid()); + ourLog.debug("Resource previously linked. Using existing link."); + retval.add(new MatchedPersonCandidate(personPid, oLink.get().getMatchResult())); + } + } + return retval; + } + + /** + * Attempt to find matching Persons by resolving them from similar Matching target resources, where target resource + * can be either Patient or Practitioner. Runs EMPI logic over the existing Patient/Practitioners, then finds their + * entries in the EmpiLink table, and returns all the matches found therein. + * + * @param theBaseResource the {@link IBaseResource} which we want to find candidate Persons for. + * @return an Optional list of {@link MatchedPersonCandidate} indicating matches. + */ + private List attemptToFindPersonCandidateFromSimilarTargetResource(IAnyResource theBaseResource) { + List retval = new ArrayList<>(); + + List personPidsToExclude = getNoMatchPersonPids(theBaseResource); + List matchedCandidates = myEmpiMatchFinderSvc.getMatchedTargets(myFhirContext.getResourceType(theBaseResource), theBaseResource); + + //Convert all possible match targets to their equivalent Persons by looking up in the EmpiLink table, + //while ensuring that the matches aren't in our NO_MATCH list. + // The data flow is as follows -> + // MatchedTargetCandidate -> Person -> EmpiLink -> MatchedPersonCandidate + matchedCandidates = matchedCandidates.stream().filter(mc -> mc.isMatch() || mc.isPossibleMatch()).collect(Collectors.toList()); + for (MatchedTarget match : matchedCandidates) { + Optional optMatchEmpiLink = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(match.getTarget())); + if (!optMatchEmpiLink.isPresent()) { + continue; + } + + EmpiLink matchEmpiLink = optMatchEmpiLink.get(); + if (personPidsToExclude.contains(matchEmpiLink.getPersonPid())) { + ourLog.info("Skipping EMPI on candidate person with PID {} due to manual NO_MATCH", matchEmpiLink.getPersonPid()); + continue; + } + + MatchedPersonCandidate candidate = new MatchedPersonCandidate(getResourcePersistentId(matchEmpiLink.getPersonPid()), match.getMatchResult()); + retval.add(candidate); + } + return retval; + } + + private List getNoMatchPersonPids(IBaseResource theBaseResource) { + Long targetPid = myIdHelperService.getPidOrNull(theBaseResource); + return myEmpiLinkDaoSvc.getEmpiLinksByTargetPidAndMatchResult(targetPid, EmpiMatchResultEnum.NO_MATCH) + .stream() + .map(EmpiLink::getPersonPid) + .collect(Collectors.toList()); + } + + private ResourcePersistentId getResourcePersistentId(Long thePersonPid) { + return new ResourcePersistentId(thePersonPid); + } + + public IAnyResource getPersonFromMatchedPersonCandidate(MatchedPersonCandidate theMatchedPersonCandidate) { + ResourcePersistentId personPid = theMatchedPersonCandidate.getCandidatePersonPid(); + return myEmpiResourceDaoSvc.readPersonByPid(personPid); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcImpl.java new file mode 100644 index 00000000000..592643acef5 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcImpl.java @@ -0,0 +1,117 @@ +package ca.uhn.fhir.jpa.empi.svc; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.IEmpiLinkSvc; +import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.empi.model.EmpiTransactionContext; +import ca.uhn.fhir.empi.util.PersonHelper; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +public class EmpiPersonMergerSvcImpl implements IEmpiPersonMergerSvc { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + PersonHelper myPersonHelper; + @Autowired + EmpiLinkDaoSvc myEmpiLinkDaoSvc; + @Autowired + IEmpiLinkSvc myEmpiLinkSvc; + @Autowired + IdHelperService myIdHelperService; + @Autowired + EmpiResourceDaoSvc myEmpiResourceDaoSvc; + + @Override + @Transactional + public IAnyResource mergePersons(IAnyResource thePersonToDelete, IAnyResource thePersonToKeep, EmpiTransactionContext theEmpiTransactionContext) { + // TODO EMPI replace this with a post containing the manually merged fields + myPersonHelper.mergePersonFields(thePersonToDelete, thePersonToKeep); + mergeLinks(thePersonToDelete, thePersonToKeep, theEmpiTransactionContext); + myEmpiResourceDaoSvc.updatePerson(thePersonToKeep); + log(theEmpiTransactionContext, "Merged " + thePersonToDelete.getIdElement().toVersionless() + " into " + thePersonToKeep.getIdElement().toVersionless()); + myEmpiResourceDaoSvc.deletePerson(thePersonToDelete); + log(theEmpiTransactionContext, "Deleted " + thePersonToDelete.getIdElement().toVersionless()); + return thePersonToKeep; + } + + private void mergeLinks(IAnyResource thePersonToDelete, IAnyResource thePersonToKeep, EmpiTransactionContext theEmpiTransactionContext) { + long personToKeepPid = myIdHelperService.getPidOrThrowException(thePersonToKeep); + List incomingLinks = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(thePersonToDelete); + List origLinks = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(thePersonToKeep); + + // For each incomingLink, either ignore it, move it, or replace the original one + + for (EmpiLink incomingLink : incomingLinks) { + Optional optionalOrigLink = findLinkWithMatchingTarget(origLinks, incomingLink); + if (optionalOrigLink.isPresent()) { + // The original links already contain this target, so move it over to the personToKeep + EmpiLink origLink = optionalOrigLink.get(); + if (incomingLink.isManual()) { + switch (origLink.getLinkSource()) { + case AUTO: + ourLog.trace("MANUAL overrides AUT0. Deleting link {}", origLink); + myEmpiLinkDaoSvc.deleteLink(origLink); + break; + case MANUAL: + if (incomingLink.getMatchResult() != origLink.getMatchResult()) { + throw new InvalidRequestException("A MANUAL " + incomingLink.getMatchResult() + " link may not be merged into a MANUAL " + origLink.getMatchResult() + " link for the same target"); + } + } + } else { + // Ignore the case where the incoming link is AUTO + continue; + } + } + // The original links didn't contain this target, so move it over to the personToKeep + incomingLink.setPersonPid(personToKeepPid); + ourLog.trace("Saving link {}", incomingLink); + myEmpiLinkDaoSvc.save(incomingLink); + } + + myEmpiLinkSvc.syncEmpiLinksToPersonLinks(thePersonToDelete, theEmpiTransactionContext); + myEmpiLinkSvc.syncEmpiLinksToPersonLinks(thePersonToKeep, theEmpiTransactionContext); + } + + private Optional findLinkWithMatchingTarget(List theEmpiLinks, EmpiLink theLinkWithTargetToMatch) { + return theEmpiLinks.stream() + .filter(empiLink -> empiLink.getTargetPid().equals(theLinkWithTargetToMatch.getTargetPid())) + .findFirst(); + } + + private void log(EmpiTransactionContext theEmpiTransactionContext, String theMessage) { + theEmpiTransactionContext.addTransactionLogMessage(theMessage); + ourLog.debug(theMessage); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiResourceDaoSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiResourceDaoSvc.java new file mode 100644 index 00000000000..2d1f4fe50f5 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiResourceDaoSvc.java @@ -0,0 +1,92 @@ +package ca.uhn.fhir.jpa.empi.svc; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.IEmpiSettings; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; +import ca.uhn.fhir.rest.param.TokenParam; +import org.hl7.fhir.instance.model.api.IAnyResource; +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.stereotype.Service; + +import javax.annotation.PostConstruct; + +@Service +public class EmpiResourceDaoSvc { + @Autowired + DaoRegistry myDaoRegistry; + @Autowired + IEmpiSettings myEmpiConfig; + + private IFhirResourceDao myPatientDao; + private IFhirResourceDao myPersonDao; + private IFhirResourceDao myPractitionerDao; + + @PostConstruct + public void postConstruct() { + myPatientDao = myDaoRegistry.getResourceDao("Patient"); + myPersonDao = myDaoRegistry.getResourceDao("Person"); + myPractitionerDao = myDaoRegistry.getResourceDao("Practitioner"); + } + + public IAnyResource readPatient(IIdType theId) { + return (IAnyResource) myPatientDao.read(theId); + } + + public IAnyResource readPerson(IIdType theId) { + return (IAnyResource) myPersonDao.read(theId); + } + + public IAnyResource readPractitioner(IIdType theId) { + return (IAnyResource) myPractitionerDao.read(theId); + } + + public DaoMethodOutcome updatePerson(IAnyResource thePerson) { + return myPersonDao.update(thePerson); + } + + public IAnyResource readPersonByPid(ResourcePersistentId thePersonPid) { + return (IAnyResource) myPersonDao.readByPid(thePersonPid); + } + + public IAnyResource searchPersonByEid(String theEidFromResource) { + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add("identifier", new TokenParam(myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem(), theEidFromResource)); + IBundleProvider search = myPersonDao.search(map); + if (search.isEmpty()) { + return null; + } else { + return (IAnyResource) search.getResources(0, 1).get(0); + } + } + + public void deletePerson(IAnyResource thePersonToDelete) { + myPersonDao.delete(thePersonToDelete.getIdElement()); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiSearchParamSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiSearchParamSvc.java new file mode 100644 index 00000000000..195abe1b4fa --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiSearchParamSvc.java @@ -0,0 +1,58 @@ +package ca.uhn.fhir.jpa.empi.svc; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.context.RuntimeSearchParam; +import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson; +import ca.uhn.fhir.jpa.searchparam.MatchUrlService; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class EmpiSearchParamSvc { + @Autowired + FhirContext myFhirContext; + @Autowired + private MatchUrlService myMatchUrlService; + @Autowired + private ISearchParamRegistry mySearchParamRegistry; + @Autowired + private SearchParamExtractorService mySearchParamExtractorService; + + public SearchParameterMap mapFromCriteria(String theResourceType, String theResourceCriteria) { + RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theResourceType); + return myMatchUrlService.translateMatchUrl(theResourceCriteria, resourceDef); + } + + public List getValueFromResourceForSearchParam(IBaseResource theResource, EmpiResourceSearchParamJson theFilterSearchParam) { + String resourceType = myFhirContext.getResourceType(theResource); + RuntimeSearchParam activeSearchParam = mySearchParamRegistry.getActiveSearchParam(resourceType, theFilterSearchParam.getSearchParam()); + return mySearchParamExtractorService.extractParamValuesAsStrings(activeSearchParam, theResource); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/MatchedPersonCandidate.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/MatchedPersonCandidate.java new file mode 100644 index 00000000000..537775fe2bd --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/MatchedPersonCandidate.java @@ -0,0 +1,47 @@ +package ca.uhn.fhir.jpa.empi.svc; + +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; + +public class MatchedPersonCandidate { + + private final ResourcePersistentId myCandidatePersonPid; + private final EmpiMatchResultEnum myEmpiMatchResult; + + public MatchedPersonCandidate(ResourcePersistentId theCandidate, EmpiMatchResultEnum theEmpiMatchResult) { + myCandidatePersonPid = theCandidate; + myEmpiMatchResult = theEmpiMatchResult; + } + + public ResourcePersistentId getCandidatePersonPid() { + return myCandidatePersonPid; + } + + public EmpiMatchResultEnum getMatchResult() { + return myEmpiMatchResult; + } + + public boolean isMatch() { + return myEmpiMatchResult == EmpiMatchResultEnum.MATCH; + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java new file mode 100644 index 00000000000..bf25f1aca49 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java @@ -0,0 +1,340 @@ +package ca.uhn.fhir.jpa.empi; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.api.EmpiConstants; +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.model.EmpiTransactionContext; +import ca.uhn.fhir.empi.rules.svc.EmpiResourceComparatorSvc; +import ca.uhn.fhir.empi.util.EIDHelper; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.data.IEmpiLinkDao; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.empi.config.EmpiConsumerConfig; +import ca.uhn.fhir.jpa.empi.config.EmpiSubmitterConfig; +import ca.uhn.fhir.jpa.empi.config.TestEmpiConfigR4; +import ca.uhn.fhir.jpa.empi.matcher.IsLinkedTo; +import ca.uhn.fhir.jpa.empi.matcher.IsMatchedToAPerson; +import ca.uhn.fhir.jpa.empi.matcher.IsPossibleDuplicateOf; +import ca.uhn.fhir.jpa.empi.matcher.IsPossibleLinkedTo; +import ca.uhn.fhir.jpa.empi.matcher.IsPossibleMatchWith; +import ca.uhn.fhir.jpa.empi.matcher.IsSamePersonAs; +import ca.uhn.fhir.jpa.empi.svc.EmpiMatchLinkSvc; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; +import ca.uhn.fhir.jpa.test.BaseJpaR4Test; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import org.hamcrest.Matcher; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.ContactPoint; +import org.hl7.fhir.r4.model.DateType; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.Practitioner; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.slf4j.LoggerFactory.getLogger; + +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = {EmpiSubmitterConfig.class, EmpiConsumerConfig.class, TestEmpiConfigR4.class, SubscriptionProcessorConfig.class}) +abstract public class BaseEmpiR4Test extends BaseJpaR4Test { + private static final Logger ourLog = getLogger(BaseEmpiR4Test.class); + + protected static final String TEST_ID_SYSTEM = "http://a.tv/"; + protected static final String JANE_ID = "ID.JANE.123"; + public static final String NAME_GIVEN_JANE = "Jane"; + protected static final String PAUL_ID = "ID.PAUL.456"; + public static final String NAME_GIVEN_PAUL = "Paul"; + public static final String TEST_NAME_FAMILY = "Doe"; + private static final ContactPoint TEST_TELECOM = new ContactPoint() + .setSystem(ContactPoint.ContactPointSystem.PHONE) + .setValue("555-555-5555"); + + @Autowired + protected FhirContext myFhirContext; + @Autowired + protected IFhirResourceDao myPersonDao; + @Autowired + protected IFhirResourceDao myPatientDao; + @Autowired + protected IFhirResourceDao myPractitionerDao; + @Autowired + protected EmpiResourceComparatorSvc myEmpiResourceComparatorSvc; + @Autowired + protected IEmpiLinkDao myEmpiLinkDao; + @Autowired + protected EmpiLinkDaoSvc myEmpiLinkDaoSvc; + @Autowired + protected IdHelperService myIdHelperService; + @Autowired + protected IEmpiSettings myEmpiConfig; + @Autowired + protected EmpiMatchLinkSvc myEmpiMatchLinkSvc; + @Autowired + protected EIDHelper myEIDHelper; + + protected ServletRequestDetails myRequestDetails = new ServletRequestDetails(null); + + @After + public void after() { + myEmpiLinkDao.deleteAll(); + super.after(); + } + + @Nonnull + protected Person createUnmanagedPerson() { + return createPerson(new Person(), false); + } + + @Nonnull + protected Person createPerson() { + return createPerson(new Person(), true); + } + + @Nonnull + protected Patient createPatient() { + return createPatient(new Patient()); + } + + @Nonnull + protected Person createPerson(Person thePerson) { + return createPerson(thePerson, true); + } + + @Nonnull + protected Person createPerson(Person thePerson, boolean theEmpiManaged) { + if (theEmpiManaged) { + thePerson.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED); + } + DaoMethodOutcome outcome = myPersonDao.create(thePerson); + Person person = (Person) outcome.getResource(); + person.setId(outcome.getId()); + return person; + } + + @Nonnull + protected Patient createPatient(Patient thePatient) { + //Note that since our empi-rules block on active=true, all patients must be active. + thePatient.setActive(true); + DaoMethodOutcome outcome = myPatientDao.create(thePatient); + Patient patient = (Patient) outcome.getResource(); + patient.setId(outcome.getId()); + return patient; + } + + @Nonnull + protected Patient buildPatientWithNameAndId(String theGivenName, String theId) { + return buildPatientWithNameIdAndBirthday(theGivenName, theId, null); + } + + @Nonnull + protected Practitioner buildPractitionerWithNameAndId(String theGivenName, String theId) { + return buildPractitionerWithNameIdAndBirthday(theGivenName, theId, null); + } + + @Nonnull + protected Person buildPersonWithNameAndId(String theGivenName, String theId) { + return buildPersonWithNameIdAndBirthday(theGivenName, theId, null); + } + + + @Nonnull + protected Patient buildPatientWithNameIdAndBirthday(String theGivenName, String theId, Date theBirthday) { + Patient patient = new Patient(); + patient.getNameFirstRep().addGiven(theGivenName); + patient.getNameFirstRep().setFamily(TEST_NAME_FAMILY); + patient.addIdentifier().setSystem(TEST_ID_SYSTEM).setValue(theId); + patient.setBirthDate(theBirthday); + patient.setTelecom(Collections.singletonList(TEST_TELECOM)); + DateType dateType = new DateType(theBirthday); + dateType.setPrecision(TemporalPrecisionEnum.DAY); + patient.setBirthDateElement(dateType); + return patient; + } + + @Nonnull + protected Practitioner buildPractitionerWithNameIdAndBirthday(String theGivenName, String theId, Date theBirthday) { + Practitioner practitioner = new Practitioner(); + practitioner.addName().addGiven(theGivenName); + practitioner.addName().setFamily(TEST_NAME_FAMILY); + practitioner.addIdentifier().setSystem(TEST_ID_SYSTEM).setValue(theId); + practitioner.setBirthDate(theBirthday); + practitioner.setTelecom(Collections.singletonList(TEST_TELECOM)); + DateType dateType = new DateType(theBirthday); + dateType.setPrecision(TemporalPrecisionEnum.DAY); + practitioner.setBirthDateElement(dateType); + return practitioner; + } + + @Nonnull + protected Person buildPersonWithNameIdAndBirthday(String theGivenName, String theId, Date theBirthday) { + Person person = new Person(); + person.addName().addGiven(theGivenName); + person.addName().setFamily(TEST_NAME_FAMILY); + person.addIdentifier().setSystem(TEST_ID_SYSTEM).setValue(theId); + person.setBirthDate(theBirthday); + DateType dateType = new DateType(theBirthday); + dateType.setPrecision(TemporalPrecisionEnum.DAY); + person.setBirthDateElement(dateType); + return person; + } + + @Nonnull + protected Patient buildJanePatient() { + return buildPatientWithNameAndId(NAME_GIVEN_JANE, JANE_ID); + } + + @Nonnull + protected Practitioner buildJanePractitioner() { + return buildPractitionerWithNameAndId(NAME_GIVEN_JANE, JANE_ID); + } + + @Nonnull + protected Person buildJanePerson() { + return buildPersonWithNameAndId(NAME_GIVEN_JANE, JANE_ID); + } + + @Nonnull + protected Patient buildPaulPatient() { + return buildPatientWithNameAndId(NAME_GIVEN_PAUL, PAUL_ID); + } + + @Nonnull + protected Patient buildJaneWithBirthday(Date theToday) { + return buildPatientWithNameIdAndBirthday(NAME_GIVEN_JANE, JANE_ID, theToday); + } + + protected void assertLinkCount(long theExpectedCount) { + assertEquals(theExpectedCount, myEmpiLinkDao.count()); + } + + protected Person getPersonFromTarget(IAnyResource theBaseResource) { + Optional matchedLinkForTargetPid = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(theBaseResource)); + if (matchedLinkForTargetPid.isPresent()) { + Long personPid = matchedLinkForTargetPid.get().getPersonPid(); + return (Person) myPersonDao.readByPid(new ResourcePersistentId(personPid)); + } else { + return null; + } + } + + protected Person getPersonFromEmpiLink(EmpiLink theEmpiLink) { + return (Person) myPersonDao.readByPid(new ResourcePersistentId(theEmpiLink.getPersonPid())); + } + + protected Patient addExternalEID(Patient thePatient, String theEID) { + thePatient.addIdentifier().setSystem(myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem()).setValue(theEID); + return thePatient; + } + + protected Patient clearExternalEIDs(Patient thePatient) { + thePatient.getIdentifier().removeIf(theIdentifier -> theIdentifier.getSystem().equalsIgnoreCase(myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem())); + return thePatient; + } + + protected Patient createPatientAndUpdateLinks(Patient thePatient) { + thePatient = createPatient(thePatient); + myEmpiMatchLinkSvc.updateEmpiLinksForEmpiTarget(thePatient, createContextForCreate()); + return thePatient; + } + + protected EmpiTransactionContext createContextForCreate() { + EmpiTransactionContext ctx = new EmpiTransactionContext(); + ctx.setRestOperation(EmpiTransactionContext.OperationType.CREATE); + ctx.setTransactionLogMessages(null); + return ctx; + } + + protected EmpiTransactionContext createContextForUpdate() { + EmpiTransactionContext ctx = new EmpiTransactionContext(); + ctx.setRestOperation(EmpiTransactionContext.OperationType.UPDATE); + ctx.setTransactionLogMessages(null); + return ctx; + } + + protected Patient updatePatientAndUpdateLinks(Patient thePatient) { + thePatient = (Patient) myPatientDao.update(thePatient).getResource(); + myEmpiMatchLinkSvc.updateEmpiLinksForEmpiTarget(thePatient, createContextForUpdate()); + return thePatient; + } + + protected Practitioner createPractitionerAndUpdateLinks(Practitioner thePractitioner) { + thePractitioner.setActive(true); + DaoMethodOutcome daoMethodOutcome = myPractitionerDao.create(thePractitioner); + thePractitioner.setId(daoMethodOutcome.getId()); + myEmpiMatchLinkSvc.updateEmpiLinksForEmpiTarget(thePractitioner, createContextForCreate()); + return thePractitioner; + } + + protected Matcher samePersonAs(IAnyResource... theBaseResource) { + return IsSamePersonAs.samePersonAs(myIdHelperService, myEmpiLinkDaoSvc, theBaseResource); + } + + protected Matcher linkedTo(IAnyResource... theBaseResource) { + return IsLinkedTo.linkedTo(myIdHelperService, myEmpiLinkDaoSvc, theBaseResource); + } + + protected Matcher possibleLinkedTo(IAnyResource... theBaseResource) { + return IsPossibleLinkedTo.possibleLinkedTo(myIdHelperService, myEmpiLinkDaoSvc, theBaseResource); + } + + protected Matcher possibleMatchWith(IAnyResource... theBaseResource) { + return IsPossibleMatchWith.possibleMatchWith(myIdHelperService, myEmpiLinkDaoSvc, theBaseResource); + } + + protected Matcher possibleDuplicateOf(IAnyResource... theBaseResource) { + return IsPossibleDuplicateOf.possibleDuplicateOf(myIdHelperService, myEmpiLinkDaoSvc, theBaseResource); + } + + protected Matcher matchedToAPerson() { + return IsMatchedToAPerson.matchedToAPerson(myIdHelperService, myEmpiLinkDaoSvc); + } + + protected Person getOnlyPerson() { + List resources = getAllPersons(); + assertEquals(1, resources.size()); + return (Person) resources.get(0); + } + + @NotNull + protected List getAllPersons() { + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + IBundleProvider bundle = myPersonDao.search(map); + return bundle.getResources(0, 999); + } + + @Nonnull + protected EmpiLink createResourcesAndBuildTestEmpiLink() { + Person person = createPerson(); + Patient patient = createPatient(); + + EmpiLink empiLink = new EmpiLink(); + empiLink.setLinkSource(EmpiLinkSourceEnum.MANUAL); + empiLink.setMatchResult(EmpiMatchResultEnum.MATCH); + empiLink.setPersonPid(myIdHelperService.getPidOrNull(person)); + empiLink.setTargetPid(myIdHelperService.getPidOrNull(patient)); + return empiLink; + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/config/BaseTestEmpiConfig.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/config/BaseTestEmpiConfig.java new file mode 100644 index 00000000000..ea27a5ec876 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/config/BaseTestEmpiConfig.java @@ -0,0 +1,41 @@ +package ca.uhn.fhir.jpa.empi.config; + +import ca.uhn.fhir.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.rules.config.EmpiSettings; +import ca.uhn.fhir.jpa.empi.helper.EmpiLinkHelper; +import com.google.common.base.Charsets; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; + +import java.io.IOException; + +@Configuration +public abstract class BaseTestEmpiConfig { + + @Value("${empi.prevent_eid_updates:true}") + boolean myPreventEidUpdates; + + @Value("${empi.prevent_multiple_eids:true}") + boolean myPreventMultipleEids; + + @Bean + IEmpiSettings empiProperties() throws IOException { + DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); + Resource resource = resourceLoader.getResource("empi/empi-rules.json"); + String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8); + return new EmpiSettings() + .setEnabled(false) + .setScriptText(json) + .setPreventEidUpdates(myPreventEidUpdates) + .setPreventMultipleEids(myPreventMultipleEids); + } + + @Bean + EmpiLinkHelper empiLinkHelper() { + return new EmpiLinkHelper(); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/config/TestEmpiConfigR4.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/config/TestEmpiConfigR4.java new file mode 100644 index 00000000000..50ae0500ed6 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/config/TestEmpiConfigR4.java @@ -0,0 +1,9 @@ +package ca.uhn.fhir.jpa.empi.config; + +import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; +import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; +import org.springframework.context.annotation.Import; + +@Import({SubscriptionSubmitterConfig.class, SubscriptionChannelConfig.class}) +public class TestEmpiConfigR4 extends BaseTestEmpiConfig { +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvcTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvcTest.java new file mode 100644 index 00000000000..5d9b8d4f57a --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvcTest.java @@ -0,0 +1,43 @@ +package ca.uhn.fhir.jpa.empi.dao; + +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.jpa.util.TestUtil; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +public class EmpiLinkDaoSvcTest extends BaseEmpiR4Test { + @Autowired + EmpiLinkDaoSvc myEmpiLinkDaoSvc; + + @Test + public void testCreate() { + EmpiLink empiLink = createResourcesAndBuildTestEmpiLink(); + assertThat(empiLink.getCreated(), is(nullValue())); + assertThat(empiLink.getUpdated(), is(nullValue())); + myEmpiLinkDaoSvc.save(empiLink); + assertThat(empiLink.getCreated(), is(notNullValue())); + assertThat(empiLink.getUpdated(), is(notNullValue())); + assertEquals(empiLink.getCreated(), empiLink.getUpdated()); + } + + @Test + public void testUpdate() { + EmpiLink createdLink = myEmpiLinkDaoSvc.save(createResourcesAndBuildTestEmpiLink()); + assertThat(createdLink.getLinkSource(), is(EmpiLinkSourceEnum.MANUAL)); + TestUtil.sleepOneClick(); + createdLink.setLinkSource(EmpiLinkSourceEnum.AUTO); + EmpiLink updatedLink = myEmpiLinkDaoSvc.save(createdLink); + assertNotEquals(updatedLink.getCreated(), updatedLink.getUpdated()); + } + +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/entity/EmpiEnumTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/entity/EmpiEnumTest.java new file mode 100644 index 00000000000..232161adcda --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/entity/EmpiEnumTest.java @@ -0,0 +1,20 @@ +package ca.uhn.fhir.jpa.empi.entity; + +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class EmpiEnumTest { + @Test + public void empiEnumOrdinals() { + // This test is here to enforce that new values in these enums are always added to the end + + assertEquals(4, EmpiMatchResultEnum.values().length); + assertEquals(EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiMatchResultEnum.values()[EmpiMatchResultEnum.values().length - 1]); + + assertEquals(2, EmpiLinkSourceEnum.values().length); + assertEquals(EmpiLinkSourceEnum.MANUAL, EmpiLinkSourceEnum.values()[EmpiLinkSourceEnum.values().length - 1]); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/BaseEmpiHelper.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/BaseEmpiHelper.java new file mode 100644 index 00000000000..3bf98d4e006 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/BaseEmpiHelper.java @@ -0,0 +1,110 @@ +package ca.uhn.fhir.jpa.empi.helper; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.empi.broker.EmpiQueueConsumerLoader; +import ca.uhn.fhir.jpa.empi.config.EmpiSubscriptionLoader; +import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannel; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.test.concurrency.PointcutLatch; +import org.junit.rules.ExternalResource; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.servlet.http.HttpServletRequest; + +import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.when; + +/** + * How to use this Rule: + *

+ * This rule is to be used whenever you want to have the EmpiInterceptor loaded, and be able + * to execute creates/updates/deletes while being assured that all EMPI work has been done before exiting. + * Provides two types of method: + *

+ * 1. doUpdate/doCreate. These methods do not wait for Asynchronous EMPI work to be done. Use these when you are expecting + * the calls to fail, as those hooks will never be called. + *

+ * 2. createWithLatch/updateWithLatch. These methods will await the EMPI hooks, which are only triggered post-EMPI processing + * You should use these when you are expecting successful processing of the resource, and need to wait for async EMPI linking + * work to be done. + *

+ * Note: all create/update functions take an optional isExternalHttpRequest boolean, to make it appear as though the request's + * origin is an HTTP request. + */ +public abstract class BaseEmpiHelper extends ExternalResource { + @Mock + protected ServletRequestDetails myMockSrd; + @Mock + protected HttpServletRequest myMockServletRequest; + @Mock + protected RestfulServer myMockRestfulServer; + @Mock + protected FhirContext myMockFhirContext; + @Mock + private IInterceptorBroadcaster myMockInterceptorBroadcaster; + + @Autowired + private IInterceptorService myInterceptorService; + @Autowired + EmpiQueueConsumerLoader myEmpiQueueConsumerLoader; + @Autowired + SubscriptionRegistry mySubscriptionRegistry; + @Autowired + SubscriptionLoader mySubscriptionLoader; + @Autowired + EmpiSubscriptionLoader myEmpiSubscriptionLoader; + + protected PointcutLatch myAfterEmpiLatch = new PointcutLatch(Pointcut.EMPI_AFTER_PERSISTED_RESOURCE_CHECKED); + + @Override + protected void before() throws Throwable { + super.before(); + //This sets up mock servlet request details, which allows our DAO requests to appear as though + //they are coming from an external HTTP Request. + MockitoAnnotations.initMocks(this); + when(myMockSrd.getInterceptorBroadcaster()).thenReturn(myMockInterceptorBroadcaster); + when(myMockSrd.getServletRequest()).thenReturn(myMockServletRequest); + when(myMockSrd.getServer()).thenReturn(myMockRestfulServer); + when(myMockSrd.getRequestId()).thenReturn("MOCK_REQUEST"); + when(myMockRestfulServer.getFhirContext()).thenReturn(myMockFhirContext); + + //This sets up our basic interceptor, and also attached the latch so we can await the hook calls. + myInterceptorService.registerAnonymousInterceptor(Pointcut.EMPI_AFTER_PERSISTED_RESOURCE_CHECKED, myAfterEmpiLatch); + + // We need to call this because subscriptions will get deleted in @After cleanup + waitForActivatedSubscriptionCount(0); + myEmpiSubscriptionLoader.daoUpdateEmpiSubscriptions(); + mySubscriptionLoader.syncSubscriptions(); + waitForActivatedSubscriptionCount(2); + } + + protected void waitForActivatedSubscriptionCount(int theSize) { + await("Active Subscription Count has reached " + theSize).until(() -> mySubscriptionRegistry.size() >= theSize); + } + + @Override + protected void after() { + myInterceptorService.unregisterInterceptor(myAfterEmpiLatch); + myAfterEmpiLatch.clear(); + waitUntilEmpiQueueIsEmpty(); + } + + private void waitUntilEmpiQueueIsEmpty() { + await().until(() -> getExecutorQueueSize() == 0); + } + + public int getExecutorQueueSize() { + LinkedBlockingChannel channel = (LinkedBlockingChannel) myEmpiQueueConsumerLoader.getEmpiChannelForUnitTest(); + return channel.getQueueSizeForUnitTest(); + } + + +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/EmpiHelperConfig.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/EmpiHelperConfig.java new file mode 100644 index 00000000000..ca38066af87 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/EmpiHelperConfig.java @@ -0,0 +1,41 @@ +package ca.uhn.fhir.jpa.empi.helper; + +import ca.uhn.fhir.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.rules.config.EmpiSettings; +import com.google.common.base.Charsets; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; + +import java.io.IOException; + +public class EmpiHelperConfig { + @Bean + public EmpiHelperR4 empiHelperR4() { + return new EmpiHelperR4(); + } + + @Value("${empi.prevent_eid_updates:false}") + boolean myPreventEidUpdates; + + @Value("${empi.prevent_multiple_eids:true}") + boolean myPreventMultipleEids; + + @Primary + @Bean + IEmpiSettings empiProperties() throws IOException { + DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); + Resource resource = resourceLoader.getResource("empi/empi-rules.json"); + String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8); + + // Set Enabled to true, and set strict mode. + return new EmpiSettings() + .setEnabled(true) + .setScriptText(json) + .setPreventEidUpdates(myPreventEidUpdates) + .setPreventMultipleEids(myPreventMultipleEids); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/EmpiHelperR4.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/EmpiHelperR4.java new file mode 100644 index 00000000000..9e7a3f19c00 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/EmpiHelperR4.java @@ -0,0 +1,75 @@ +package ca.uhn.fhir.jpa.empi.helper; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.rest.server.TransactionLogMessages; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.annotation.Autowired; + +public class EmpiHelperR4 extends BaseEmpiHelper { + @Autowired + private FhirContext myFhirContext; + @Autowired + private DaoRegistry myDaoRegistry; + + public OutcomeAndLogMessageWrapper createWithLatch(IBaseResource theResource) throws InterruptedException { + return createWithLatch(theResource, true); + } + + public OutcomeAndLogMessageWrapper createWithLatch(IBaseResource theBaseResource, boolean isExternalHttpRequest) throws InterruptedException { + myAfterEmpiLatch.setExpectedCount(1); + DaoMethodOutcome daoMethodOutcome = doCreateResource(theBaseResource, isExternalHttpRequest); + myAfterEmpiLatch.awaitExpected(); + return new OutcomeAndLogMessageWrapper(daoMethodOutcome, myAfterEmpiLatch.getLatchInvocationParameterOfType(TransactionLogMessages.class)); + } + + public OutcomeAndLogMessageWrapper updateWithLatch(IBaseResource theIBaseResource) throws InterruptedException { + return updateWithLatch(theIBaseResource, true); + } + + public OutcomeAndLogMessageWrapper updateWithLatch(IBaseResource theIBaseResource, boolean isExternalHttpRequest) throws InterruptedException { + myAfterEmpiLatch.setExpectedCount(1); + DaoMethodOutcome daoMethodOutcome = doUpdateResource(theIBaseResource, isExternalHttpRequest); + myAfterEmpiLatch.awaitExpected(); + return new OutcomeAndLogMessageWrapper(daoMethodOutcome, myAfterEmpiLatch.getLatchInvocationParameterOfType(TransactionLogMessages.class)); + } + + public DaoMethodOutcome doCreateResource(IBaseResource theResource, boolean isExternalHttpRequest) { + String resourceType = myFhirContext.getResourceType(theResource); + + IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceType); + return isExternalHttpRequest ? dao.create(theResource, myMockSrd): dao.create(theResource); + } + + public DaoMethodOutcome doUpdateResource(IBaseResource theResource, boolean isExternalHttpRequest) { + String resourceType = myFhirContext.getResourceType(theResource); + IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceType); + return isExternalHttpRequest ? dao.update(theResource, myMockSrd): dao.create(theResource); + } + + /** + * OutcomeAndLogMessageWrapper is a simple wrapper class which is _excellent_. It allows us to skip the fact that java doesn't allow + * multiple returns, and wraps both the Method Outcome of the DAO, _and_ the TransactionLogMessages that were passed to the pointcut + * by the EMPI module. + */ + public class OutcomeAndLogMessageWrapper { + DaoMethodOutcome myDaoMethodOutcome; + TransactionLogMessages myLogMessages; + + private OutcomeAndLogMessageWrapper(DaoMethodOutcome theDaoMethodOutcome, TransactionLogMessages theTransactionLogMessages) { + myDaoMethodOutcome = theDaoMethodOutcome; + myLogMessages = theTransactionLogMessages; + } + + public DaoMethodOutcome getDaoMethodOutcome() { + return myDaoMethodOutcome; + } + + public TransactionLogMessages getLogMessages() { + return myLogMessages; + } + } + +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/EmpiLinkHelper.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/EmpiLinkHelper.java new file mode 100644 index 00000000000..b4c0cfaed2b --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/helper/EmpiLinkHelper.java @@ -0,0 +1,31 @@ +package ca.uhn.fhir.jpa.empi.helper; + +import ca.uhn.fhir.jpa.dao.data.IEmpiLinkDao; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.model.primitive.IdDt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +public class EmpiLinkHelper { + private static final Logger ourLog = LoggerFactory.getLogger(EmpiLinkHelper.class); + + @Autowired + IEmpiLinkDao myEmpiLinkDao; + + @Transactional + public void logEmpiLinks() { + List links = myEmpiLinkDao.findAll(); + ourLog.info("All EMPI Links:"); + for (EmpiLink link : links) { + IdDt personId = link.getPerson().getIdDt().toVersionless(); + IdDt targetId = link.getTarget().getIdDt().toVersionless(); + ourLog.info("{}: {}, {}, {}, {}", link.getId(), personId, targetId, link.getMatchResult(), link.getLinkSource()); + } + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiExpungeTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiExpungeTest.java new file mode 100644 index 00000000000..051db629424 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiExpungeTest.java @@ -0,0 +1,80 @@ +package ca.uhn.fhir.jpa.empi.interceptor; + +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.model.ExpungeOptions; +import ca.uhn.fhir.jpa.dao.data.IEmpiLinkDao; +import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class EmpiExpungeTest extends BaseEmpiR4Test { + + @Autowired + IInterceptorService myInterceptorService; + @Autowired + IEmpiStorageInterceptor myEmpiStorageInterceptor; + @Autowired + DaoConfig myDaoConfig; + @Autowired + IEmpiLinkDao myEmpiLinkDao; + private ResourceTable myTargetEntity; + private ResourceTable myPersonEntity; + private IdDt myTargetId; + + @Before + public void before() { + myDaoConfig.setExpungeEnabled(true); + + myTargetEntity = (ResourceTable) myPatientDao.create(new Patient()).getEntity(); + myTargetId = myTargetEntity.getIdDt().toVersionless(); + myPersonEntity = (ResourceTable) myPersonDao.create(new Person()).getEntity(); + + EmpiLink empiLink = new EmpiLink(); + empiLink.setLinkSource(EmpiLinkSourceEnum.MANUAL); + empiLink.setMatchResult(EmpiMatchResultEnum.MATCH); + empiLink.setPersonPid(myPersonEntity.getId()); + empiLink.setTargetPid(myTargetEntity.getId()); + myEmpiLinkDaoSvc.save(empiLink); + } + + @Test + public void testUninterceptedDeleteRemovesEMPIReference() { + assertEquals(1, myEmpiLinkDao.count()); + myPatientDao.delete(myTargetEntity.getIdDt()); + assertEquals(1, myEmpiLinkDao.count()); + ExpungeOptions expungeOptions = new ExpungeOptions(); + expungeOptions.setExpungeDeletedResources(true); + try { + myPatientDao.expunge(myTargetId.toVersionless(), expungeOptions, null); + fail(); + } catch (InternalErrorException e) { + assertThat(e.getMessage(), containsString("ViolationException")); + assertThat(e.getMessage(), containsString("FK_EMPI_LINK_TARGET")); + } + myInterceptorService.registerInterceptor(myEmpiStorageInterceptor); + myPatientDao.expunge(myTargetId.toVersionless(), expungeOptions, null); + assertEquals(0, myEmpiLinkDao.count()); + } + + @After + public void afterUnregisterInterceptor() { + myInterceptorService.unregisterInterceptor(myEmpiStorageInterceptor); + } + +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptorIT.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptorIT.java new file mode 100644 index 00000000000..462e5d3c4fd --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptorIT.java @@ -0,0 +1,244 @@ +package ca.uhn.fhir.jpa.empi.interceptor; + +import ca.uhn.fhir.empi.model.CanonicalEID; +import ca.uhn.fhir.empi.rules.config.EmpiSettings; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test; +import ca.uhn.fhir.jpa.empi.helper.EmpiHelperConfig; +import ca.uhn.fhir.jpa.empi.helper.EmpiHelperR4; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; +import ca.uhn.fhir.rest.server.TransactionLogMessages; +import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.Practitioner; +import org.junit.Rule; +import org.junit.Test; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; + +import java.util.List; + +import static ca.uhn.fhir.empi.api.EmpiConstants.CODE_HAPI_EMPI_MANAGED; +import static ca.uhn.fhir.empi.api.EmpiConstants.SYSTEM_EMPI_MANAGED; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.slf4j.LoggerFactory.getLogger; + +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +@ContextConfiguration(classes = {EmpiHelperConfig.class}) +public class EmpiStorageInterceptorIT extends BaseEmpiR4Test { + + private static final Logger ourLog = getLogger(EmpiStorageInterceptorIT.class); + + @Rule + @Autowired + public EmpiHelperR4 myEmpiHelper; + @Autowired + private IdHelperService myIdHelperService; + + @Test + public void testCreatePatient() throws InterruptedException { + myEmpiHelper.createWithLatch(new Patient()); + assertLinkCount(1); + } + + @Test + public void testCreatePractitioner() throws InterruptedException { + myEmpiHelper.createWithLatch(new Practitioner()); + assertLinkCount(1); + } + + @Test + public void testCreatePerson() throws InterruptedException { + myPersonDao.create(new Person()); + assertLinkCount(0); + } + + @Test + public void testDeletePersonDeletesLinks() throws InterruptedException { + myEmpiHelper.createWithLatch(new Patient()); + assertLinkCount(1); + Person person = getOnlyPerson(); + myPersonDao.delete(person.getIdElement()); + assertLinkCount(0); + } + + @Test + public void testCreatePersonWithEmpiTagForbidden() throws InterruptedException { + //Creating a person with the EMPI-MANAGED tag should fail + Person person = new Person(); + person.getMeta().addTag(SYSTEM_EMPI_MANAGED, CODE_HAPI_EMPI_MANAGED, "User is managed by EMPI"); + try { + myEmpiHelper.doCreateResource(person, true); + fail(); + } catch (ForbiddenOperationException e) { + assertEquals("Cannot create or modify Resources that are managed by EMPI.", e.getMessage()); + } + } + + @Test + public void testCreateOrganizationWithEmpiTagForbidden() throws InterruptedException { + //Creating a organization with the EMPI-MANAGED tag should fail + Organization organization = new Organization(); + organization.getMeta().addTag(SYSTEM_EMPI_MANAGED, CODE_HAPI_EMPI_MANAGED, "User is managed by EMPI"); + try { + myEmpiHelper.doCreateResource(organization, true); + fail(); + } catch (ForbiddenOperationException e) { + assertEquals("Cannot create or modify Resources that are managed by EMPI.", e.getMessage()); + } + } + + @Test + public void testUpdateOrganizationWithEmpiTagForbidden() throws InterruptedException { + //Creating a organization with the EMPI-MANAGED tag should fail + Organization organization = new Organization(); + myEmpiHelper.doCreateResource(organization, true); + organization.getMeta().addTag(SYSTEM_EMPI_MANAGED, CODE_HAPI_EMPI_MANAGED, "User is managed by EMPI"); + try { + myEmpiHelper.doUpdateResource(organization, true); + fail(); + } catch (ForbiddenOperationException e) { + assertEquals("The HAPI-EMPI tag on a resource may not be changed once created.", e.getMessage()); + } + } + + @Test + public void testPersonRecordsManagedByEmpiAllShareSameTag() throws InterruptedException { + myEmpiHelper.createWithLatch(buildJanePatient()); + myEmpiHelper.createWithLatch(buildPaulPatient()); + + IBundleProvider search = myPersonDao.search(new SearchParameterMap().setLoadSynchronous(true)); + List resources = search.getResources(0, search.size()); + + for (IBaseResource person: resources) { + assertThat(person.getMeta().getTag(SYSTEM_EMPI_MANAGED, CODE_HAPI_EMPI_MANAGED), is(notNullValue())); + } + } + + @Test + public void testNonEmpiManagedPersonCannotHaveEmpiManagedTagAddedToThem() { + //Person created manually. + Person person = new Person(); + DaoMethodOutcome daoMethodOutcome = myEmpiHelper.doCreateResource(person, true); + assertNotNull(daoMethodOutcome.getId()); + + //Updating that person to set them as EMPI managed is not allowed. + person.getMeta().addTag(SYSTEM_EMPI_MANAGED, CODE_HAPI_EMPI_MANAGED, "User is managed by EMPI"); + try { + myEmpiHelper.doUpdateResource(person, true); + fail(); + } catch (ForbiddenOperationException e) { + assertEquals("The HAPI-EMPI tag on a resource may not be changed once created.", e.getMessage() ); + } + } + + @Test + public void testEmpiManagedPersonCannotBeModifiedByPersonUpdateRequest() throws InterruptedException { + // When EMPI is enabled, only the EMPI system is allowed to modify Person links of Persons with the EMPI-MANAGED tag. + Patient patient = new Patient(); + IIdType patientId = myEmpiHelper.createWithLatch(new Patient()).getDaoMethodOutcome().getId().toUnqualifiedVersionless(); + + patient.setId(patientId); + + //Updating a Person who was created via EMPI should fail. + EmpiLink empiLink = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(patient)).get(); + Long personPid = empiLink.getPersonPid(); + Person empiPerson= (Person)myPersonDao.readByPid(new ResourcePersistentId(personPid)); + empiPerson.setGender(Enumerations.AdministrativeGender.MALE); + try { + myEmpiHelper.doUpdateResource(empiPerson, true); + fail(); + } catch (ForbiddenOperationException e) { + assertEquals("Cannot create or modify Resources that are managed by EMPI.", e.getMessage()); + } + } + + @Test + public void testEmpiPointcutReceivesTransactionLogMessages() throws InterruptedException { + EmpiHelperR4.OutcomeAndLogMessageWrapper wrapper = myEmpiHelper.createWithLatch(buildJanePatient()); + + TransactionLogMessages empiTransactionLogMessages = wrapper.getLogMessages(); + + //There is no TransactionGuid here as there is no TransactionLog in this context. + assertThat(empiTransactionLogMessages.getTransactionGuid(), is(nullValue())); + + List messages = empiTransactionLogMessages.getValues(); + assertThat(messages.isEmpty(), is(false)); + } + + @Test + public void testWhenASingularPatientUpdatesExternalEidThatPersonEidIsUpdated() throws InterruptedException { + Patient jane = addExternalEID(buildJanePatient(), "some_eid"); + EmpiHelperR4.OutcomeAndLogMessageWrapper latch = myEmpiHelper.createWithLatch(jane); + jane.setId(latch.getDaoMethodOutcome().getId()); + clearExternalEIDs(jane); + jane = addExternalEID(jane, "some_new_eid"); + + EmpiHelperR4.OutcomeAndLogMessageWrapper outcomeWrapper = myEmpiHelper.updateWithLatch(jane); + Person person = getPersonFromTarget(jane); + List externalEids = myEIDHelper.getExternalEid(person); + assertThat(externalEids, hasSize(1)); + assertThat("some_new_eid", is(equalTo(externalEids.get(0).getValue()))); + } + @Test + public void testWhenEidUpdatesAreDisabledForbidsUpdatesToEidsOnTargets() throws InterruptedException { + setPreventEidUpdates(true); + Patient jane = addExternalEID(buildJanePatient(), "some_eid"); + EmpiHelperR4.OutcomeAndLogMessageWrapper latch = myEmpiHelper.createWithLatch(jane); + jane.setId(latch.getDaoMethodOutcome().getId()); + clearExternalEIDs(jane); + jane = addExternalEID(jane, "some_new_eid"); + try { + myEmpiHelper.doUpdateResource(jane, true); + fail(); + } catch (ForbiddenOperationException e) { + assertThat(e.getMessage(), is(equalTo("While running with EID updates disabled, EIDs may not be updated on Patient/Practitioner resources"))); + } + setPreventEidUpdates(false); + } + + @Test + public void testWhenMultipleEidsAreDisabledThatTheInterceptorRejectsCreatesWithThem() { + setPreventMultipleEids(true); + Patient patient = buildJanePatient(); + addExternalEID(patient, "123"); + addExternalEID(patient, "456"); + try { + myEmpiHelper.doCreateResource(patient, true); + fail(); + } catch (ForbiddenOperationException e) { + assertThat(e.getMessage(), is(equalTo("While running with multiple EIDs disabled, Patient/Practitioner resources may have at most one EID."))); + } + + setPreventMultipleEids(false); + + } + + private void setPreventEidUpdates(boolean thePrevent) { + ((EmpiSettings)myEmpiConfig).setPreventEidUpdates(thePrevent); + } + + private void setPreventMultipleEids(boolean thePrevent) { + ((EmpiSettings)myEmpiConfig).setPreventMultipleEids(thePrevent); + } + +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/BasePersonMatcher.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/BasePersonMatcher.java new file mode 100644 index 00000000000..5fa8a42be96 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/BasePersonMatcher.java @@ -0,0 +1,79 @@ +package ca.uhn.fhir.jpa.empi.matcher; + +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import org.hamcrest.TypeSafeMatcher; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public abstract class BasePersonMatcher extends TypeSafeMatcher { + private static final Logger ourLog = LoggerFactory.getLogger(BasePersonMatcher.class); + + protected IdHelperService myIdHelperService; + protected EmpiLinkDaoSvc myEmpiLinkDaoSvc; + protected Collection myBaseResources; + + protected BasePersonMatcher(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc, IAnyResource... theBaseResource) { + myIdHelperService = theIdHelperService; + myEmpiLinkDaoSvc = theEmpiLinkDaoSvc; + myBaseResources = Arrays.stream(theBaseResource).collect(Collectors.toList()); + } + + @Nullable + protected Long getMatchedPersonPidFromResource(IAnyResource theResource) { + Long retval; + if (isPatientOrPractitioner(theResource)) { + EmpiLink matchLink = getMatchedEmpiLink(theResource); + retval = matchLink == null ? null : matchLink.getPersonPid(); + } else if (isPerson(theResource)) { + retval = myIdHelperService.getPidOrNull(theResource); + } else { + throw new IllegalArgumentException("Resources of type " + theResource.getIdElement().getResourceType() + " cannot be persons!"); + } + return retval; + } + + protected List getPossibleMatchedPersonPidsFromTarget(IAnyResource theBaseResource) { + return getEmpiLinksForTarget(theBaseResource, EmpiMatchResultEnum.POSSIBLE_MATCH).stream().map(EmpiLink::getPersonPid).collect(Collectors.toList()); + } + + protected boolean isPatientOrPractitioner(IAnyResource theResource) { + String resourceType = theResource.getIdElement().getResourceType(); + return (resourceType.equalsIgnoreCase("Patient") || resourceType.equalsIgnoreCase("Practitioner")); + } + + protected EmpiLink getMatchedEmpiLink(IAnyResource thePatientOrPractitionerResource) { + List empiLinks = getEmpiLinksForTarget(thePatientOrPractitionerResource, EmpiMatchResultEnum.MATCH); + if (empiLinks.size() == 0) { + return null; + } else if (empiLinks.size() == 1) { + return empiLinks.get(0); + } else { + throw new IllegalStateException("Its illegal to have more than 1 match for a given target! we found " + empiLinks.size() + " for resource with id: " + thePatientOrPractitionerResource.getIdElement().toUnqualifiedVersionless()); + } + } + + protected boolean isPerson(IAnyResource theIncomingResource) { + return (theIncomingResource.getIdElement().getResourceType().equalsIgnoreCase("Person")); + } + + protected List getEmpiLinksForTarget(IAnyResource thePatientOrPractitionerResource, EmpiMatchResultEnum theMatchResult) { + Long pidOrNull = myIdHelperService.getPidOrNull(thePatientOrPractitionerResource); + List matchLinkForTarget = myEmpiLinkDaoSvc.getEmpiLinksByTargetPidAndMatchResult(pidOrNull, theMatchResult); + if (!matchLinkForTarget.isEmpty()) { + return matchLinkForTarget; + } else { + return new ArrayList<>(); + } + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsLinkedTo.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsLinkedTo.java new file mode 100644 index 00000000000..d85b6fa5b49 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsLinkedTo.java @@ -0,0 +1,48 @@ +package ca.uhn.fhir.jpa.empi.matcher; + +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hl7.fhir.instance.model.api.IAnyResource; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * A Matcher which allows us to check that a target patient/practitioner at a given link level. + * is linked to a set of patients/practitioners via a person. + * + */ +public class IsLinkedTo extends BasePersonMatcher { + + private List baseResourcePersonPids; + private Long incomingResourcePersonPid; + + protected IsLinkedTo(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc, IAnyResource... theBaseResource) { + super(theIdHelperService, theEmpiLinkDaoSvc, theBaseResource); + } + + + @Override + protected boolean matchesSafely(IAnyResource theIncomingResource) { + incomingResourcePersonPid = getMatchedPersonPidFromResource(theIncomingResource); + + //OK, lets grab all the person pids of the resources passed in via the constructor. + baseResourcePersonPids = myBaseResources.stream() + .map(this::getMatchedPersonPidFromResource) + .collect(Collectors.toList()); + + //The resources are linked if all person pids match the incoming person pid. + return baseResourcePersonPids.stream() + .allMatch(pid -> pid.equals(incomingResourcePersonPid)); + } + + @Override + public void describeTo(Description theDescription) { + } + + public static Matcher linkedTo(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc, IAnyResource... theBaseResource) { + return new IsLinkedTo(theIdHelperService, theEmpiLinkDaoSvc, theBaseResource); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsMatchedToAPerson.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsMatchedToAPerson.java new file mode 100644 index 00000000000..c8fffcb6e67 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsMatchedToAPerson.java @@ -0,0 +1,37 @@ +package ca.uhn.fhir.jpa.empi.matcher; + +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.hl7.fhir.instance.model.api.IAnyResource; + +import java.util.Optional; + +public class IsMatchedToAPerson extends TypeSafeMatcher { + + private final IdHelperService myIdHelperService; + private final EmpiLinkDaoSvc myEmpiLinkDaoSvc; + + public IsMatchedToAPerson(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc) { + myIdHelperService = theIdHelperService; + myEmpiLinkDaoSvc = theEmpiLinkDaoSvc; + } + + @Override + protected boolean matchesSafely(IAnyResource theIncomingResource) { + Optional matchedLinkForTargetPid = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(theIncomingResource)); + return matchedLinkForTargetPid.isPresent(); + } + + @Override + public void describeTo(Description theDescription) { + theDescription.appendText("patient/practitioner was not linked to a Person."); + } + + public static Matcher matchedToAPerson(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc) { + return new IsMatchedToAPerson(theIdHelperService, theEmpiLinkDaoSvc); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleDuplicateOf.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleDuplicateOf.java new file mode 100644 index 00000000000..cab1452e70b --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleDuplicateOf.java @@ -0,0 +1,61 @@ +package ca.uhn.fhir.jpa.empi.matcher; + +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hl7.fhir.instance.model.api.IAnyResource; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class IsPossibleDuplicateOf extends BasePersonMatcher { + /** + * Matcher with tells us if there is an EmpiLink with between these two resources that are considered POSSIBLE DUPLICATE. + * For use only on persons. + */ + private Long incomingPersonPid; + + protected IsPossibleDuplicateOf(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc, IAnyResource... theBaseResource) { + super(theIdHelperService, theEmpiLinkDaoSvc, theBaseResource); + } + + @Override + protected boolean matchesSafely(IAnyResource theIncomingResource) { + + incomingPersonPid = getMatchedPersonPidFromResource(theIncomingResource); + + List personPidsToMatch = myBaseResources.stream() + .map(this::getMatchedPersonPidFromResource) + .collect(Collectors.toList()); + + + //Returns true if there is a POSSIBLE_DUPLICATE between the incoming resource, and all of the resources passed in via the constructor. + return personPidsToMatch.stream() + .map(baseResourcePid -> { + Optional duplicateLink = myEmpiLinkDaoSvc.getEmpiLinksByPersonPidTargetPidAndMatchResult(baseResourcePid, incomingPersonPid, EmpiMatchResultEnum.POSSIBLE_DUPLICATE); + if (!duplicateLink.isPresent()) { + duplicateLink = myEmpiLinkDaoSvc.getEmpiLinksByPersonPidTargetPidAndMatchResult(incomingPersonPid, baseResourcePid, EmpiMatchResultEnum.POSSIBLE_DUPLICATE); + } + return duplicateLink; + }).allMatch(Optional::isPresent); + } + + @Override + public void describeTo(Description theDescription) { + theDescription.appendText("Person was not duplicate of Person/" + incomingPersonPid); + } + + @Override + protected void describeMismatchSafely(IAnyResource item, Description mismatchDescription) { + super.describeMismatchSafely(item, mismatchDescription); + mismatchDescription.appendText("No Empi Link With POSSIBLE_DUPLICATE was found"); + } + + public static Matcher possibleDuplicateOf(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc, IAnyResource... theBaseResource) { + return new IsPossibleDuplicateOf(theIdHelperService, theEmpiLinkDaoSvc, theBaseResource); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleLinkedTo.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleLinkedTo.java new file mode 100644 index 00000000000..d2248ef4e3d --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleLinkedTo.java @@ -0,0 +1,47 @@ +package ca.uhn.fhir.jpa.empi.matcher; + +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hl7.fhir.instance.model.api.IAnyResource; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * A Matcher which allows us to check that a target patient/practitioner at a given link level. + * is linked to a set of patients/practitioners via a person. + * + */ +public class IsPossibleLinkedTo extends BasePersonMatcher { + + private List baseResourcePersonPids; + private Long incomingResourcePersonPid; + + protected IsPossibleLinkedTo(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc, IAnyResource... theTargetResources) { + super(theIdHelperService, theEmpiLinkDaoSvc, theTargetResources); + } + + @Override + protected boolean matchesSafely(IAnyResource thePersonResource) { + incomingResourcePersonPid = myIdHelperService.getPidOrNull(thePersonResource);; + + //OK, lets grab all the person pids of the resources passed in via the constructor. + baseResourcePersonPids = myBaseResources.stream() + .flatMap(iBaseResource -> getPossibleMatchedPersonPidsFromTarget(iBaseResource).stream()) + .collect(Collectors.toList()); + + //The resources are linked if all person pids match the incoming person pid. + return baseResourcePersonPids.stream() + .allMatch(pid -> pid.equals(incomingResourcePersonPid)); + } + + @Override + public void describeTo(Description theDescription) { + } + + public static Matcher possibleLinkedTo(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc, IAnyResource... theBaseResource) { + return new IsPossibleLinkedTo(theIdHelperService, theEmpiLinkDaoSvc, theBaseResource); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleMatchWith.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleMatchWith.java new file mode 100644 index 00000000000..bd31637ed72 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleMatchWith.java @@ -0,0 +1,58 @@ +package ca.uhn.fhir.jpa.empi.matcher; + +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hl7.fhir.instance.model.api.IAnyResource; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Matcher with tells us if there is an EmpiLink with between these two resources that are considered POSSIBLE_MATCH + */ +public class IsPossibleMatchWith extends BasePersonMatcher { + + protected IsPossibleMatchWith(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc, IAnyResource... theBaseResource) { + super(theIdHelperService, theEmpiLinkDaoSvc, theBaseResource); + } + + @Override + protected boolean matchesSafely(IAnyResource theIncomingResource) { + List empiLinks = getEmpiLinksForTarget(theIncomingResource, EmpiMatchResultEnum.POSSIBLE_MATCH); + + List personPidsToMatch = myBaseResources.stream() + .map(this::getMatchedPersonPidFromResource) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (personPidsToMatch.isEmpty()) { + personPidsToMatch = myBaseResources.stream() + .flatMap(iBaseResource -> getPossibleMatchedPersonPidsFromTarget(iBaseResource).stream()) + .collect(Collectors.toList()); + } + + List empiLinkSourcePersonPids = empiLinks.stream().map(EmpiLink::getPersonPid).collect(Collectors.toList()); + + return empiLinkSourcePersonPids.containsAll(personPidsToMatch); + } + + @Override + public void describeTo(Description theDescription) { + theDescription.appendText(" no link found with POSSIBLE_MATCH to the requested PIDS"); + } + + @Override + protected void describeMismatchSafely(IAnyResource item, Description mismatchDescription) { + super.describeMismatchSafely(item, mismatchDescription); + mismatchDescription.appendText("No Empi Link With POSSIBLE_MATCH was found"); + } + + public static Matcher possibleMatchWith(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc, IAnyResource... theBaseResource) { + return new IsPossibleMatchWith(theIdHelperService, theEmpiLinkDaoSvc, theBaseResource); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsSamePersonAs.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsSamePersonAs.java new file mode 100644 index 00000000000..15db470f3ae --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsSamePersonAs.java @@ -0,0 +1,46 @@ +package ca.uhn.fhir.jpa.empi.matcher; + +import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc; +import ca.uhn.fhir.jpa.dao.index.IdHelperService; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hl7.fhir.instance.model.api.IAnyResource; + +import java.util.List; +import java.util.stream.Collectors; + +public class IsSamePersonAs extends BasePersonMatcher { + + private List personPidsToMatch; + private Long incomingPersonPid; + + public IsSamePersonAs(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc, IAnyResource... theBaseResource) { + super(theIdHelperService, theEmpiLinkDaoSvc, theBaseResource); + } + + @Override + protected boolean matchesSafely(IAnyResource theIncomingResource) { + incomingPersonPid = getMatchedPersonPidFromResource(theIncomingResource); + personPidsToMatch = myBaseResources.stream().map(this::getMatchedPersonPidFromResource).collect(Collectors.toList()); + boolean allToCheckAreSame = personPidsToMatch.stream().allMatch(pid -> pid.equals(personPidsToMatch.get(0))); + if (!allToCheckAreSame) { + throw new IllegalStateException("You wanted to do a person comparison, but the pool of persons you submitted for checking don't match! We won't even check the incoming person against them."); + } + return personPidsToMatch.contains(incomingPersonPid); + } + + @Override + public void describeTo(Description theDescription) { + theDescription.appendText("patient/practitioner linked to Person/" + personPidsToMatch); + } + + @Override + protected void describeMismatchSafely(IAnyResource item, Description mismatchDescription) { + super.describeMismatchSafely(item, mismatchDescription); + mismatchDescription.appendText(" was actually linked to Person/" + incomingPersonPid); + } + + public static Matcher samePersonAs(IdHelperService theIdHelperService, EmpiLinkDaoSvc theEmpiLinkDaoSvc, IAnyResource... theBaseResource) { + return new IsSamePersonAs(theIdHelperService, theEmpiLinkDaoSvc, theBaseResource); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseLinkR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseLinkR4Test.java new file mode 100644 index 00000000000..4d417adf3ae --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseLinkR4Test.java @@ -0,0 +1,47 @@ +package ca.uhn.fhir.jpa.empi.provider; + +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.StringType; +import org.junit.Before; + +import javax.annotation.Nonnull; + +import static org.junit.Assert.assertEquals; + +public abstract class BaseLinkR4Test extends BaseProviderR4Test { + protected Patient myPatient; + protected Person myPerson; + protected EmpiLink myLink; + protected StringType myPatientId; + protected StringType myPersonId; + protected StringType myNoMatch; + protected StringType myPossibleMatch; + protected StringType myPossibleDuplicate; + + @Before + public void before() { + super.before(); + + myPatient = createPatientAndUpdateLinks(new Patient()); + myPatientId = new StringType(myPatient.getIdElement().toUnqualifiedVersionless().getValue()); + + myPerson = getPersonFromTarget(myPatient); + myPersonId = new StringType(myPerson.getIdElement().toUnqualifiedVersionless().getValue()); + myLink = getLink(); + assertEquals(EmpiLinkSourceEnum.AUTO, myLink.getLinkSource()); + assertEquals(EmpiMatchResultEnum.MATCH, myLink.getMatchResult()); + + myNoMatch = new StringType(EmpiMatchResultEnum.NO_MATCH.name()); + myPossibleMatch = new StringType(EmpiMatchResultEnum.POSSIBLE_MATCH.name()); + myPossibleDuplicate = new StringType(EmpiMatchResultEnum.POSSIBLE_DUPLICATE.name()); + } + + @Nonnull + protected EmpiLink getLink() { + return myEmpiLinkDaoSvc.findEmpiLinkByTarget(myPatient).get(); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseProviderR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseProviderR4Test.java new file mode 100644 index 00000000000..ef4e72d79bc --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/BaseProviderR4Test.java @@ -0,0 +1,31 @@ +package ca.uhn.fhir.jpa.empi.provider; + +import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc; +import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc; +import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc; +import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc; +import ca.uhn.fhir.empi.provider.EmpiProviderR4; +import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test; +import ca.uhn.fhir.validation.IResourceLoader; +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; + +public abstract class BaseProviderR4Test extends BaseEmpiR4Test { + @Autowired + private IEmpiMatchFinderSvc myEmpiMatchFinderSvc; + @Autowired + private IEmpiPersonMergerSvc myPersonMergerSvc; + @Autowired + private IEmpiLinkUpdaterSvc myEmpiLinkUpdaterSvc; + @Autowired + private IEmpiLinkQuerySvc myEmpiLinkQuerySvc; + @Autowired + private IResourceLoader myResourceLoader; + + EmpiProviderR4 myEmpiProviderR4; + + @Before + public void before() { + myEmpiProviderR4 = new EmpiProviderR4(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderMergePersonsR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderMergePersonsR4Test.java new file mode 100644 index 00000000000..5289a9cfa2f --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderMergePersonsR4Test.java @@ -0,0 +1,134 @@ +package ca.uhn.fhir.jpa.empi.provider; + +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.StringType; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class EmpiProviderMergePersonsR4Test extends BaseProviderR4Test { + + private Person myDeletePerson; + private StringType myDeletePersonId; + private Person myKeepPerson; + private StringType myKeepPersonId; + + @Before + public void before() { + super.before(); + + myDeletePerson = createPerson(); + myDeletePersonId = new StringType(myDeletePerson.getIdElement().toUnqualifiedVersionless().getValue()); + myKeepPerson = createPerson(); + myKeepPersonId = new StringType(myKeepPerson.getIdElement().toUnqualifiedVersionless().getValue()); + } + + @Test + public void testMatch() { + Patient jane = buildJanePatient(); + jane.setActive(true); + Patient createdJane = createPatient(jane); + Patient newJane = buildJanePatient(); + + Bundle result = myEmpiProviderR4.match(newJane); + assertEquals(1, result.getEntry().size()); + assertEquals(createdJane.getId(), result.getEntryFirstRep().getResource().getId()); + } + + @Test + public void testMerge() { + Person mergedPerson = myEmpiProviderR4.mergePersons(myDeletePersonId, myKeepPersonId, myRequestDetails); + assertEquals(myKeepPerson.getIdElement(), mergedPerson.getIdElement()); + assertThat(mergedPerson, is(samePersonAs(mergedPerson))); + assertEquals(1, getAllPersons().size()); + } + + @Test + public void testUnmanagedMerge() { + StringType deletePersonId = new StringType(createUnmanagedPerson().getIdElement().toVersionless().getValue()); + StringType keepPersonId = new StringType(createUnmanagedPerson().getIdElement().toVersionless().getValue()); + try { + myEmpiProviderR4.mergePersons(deletePersonId, keepPersonId, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Only EMPI managed resources can be merged. Empi managed resource have the HAPI-EMPI tag.", e.getMessage()); + } + } + + @Test + public void testMergePatients() { + try { + StringType patientId = new StringType(createPatient().getIdElement().toVersionless().getValue()); + StringType otherPatientId = new StringType(createPatient().getIdElement().toVersionless().getValue()); + myEmpiProviderR4.mergePersons(patientId, otherPatientId, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("personIdToDelete must have form Person/ where is the id of the person", e.getMessage()); + } + + } + + @Test + public void testNullParams() { + try { + myEmpiProviderR4.mergePersons(null, null, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("personIdToDelete cannot be null", e.getMessage()); + } + try { + myEmpiProviderR4.mergePersons(null, myKeepPersonId, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("personIdToDelete cannot be null", e.getMessage()); + } + try { + myEmpiProviderR4.mergePersons(myDeletePersonId, null, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("personIdToKeep cannot be null", e.getMessage()); + } + } + + @Test + public void testBadParams() { + try { + myEmpiProviderR4.mergePersons(new StringType("Patient/1"), new StringType("Patient/2"), myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("personIdToDelete must have form Person/ where is the id of the person", e.getMessage()); + } + try { + myEmpiProviderR4.mergePersons(myDeletePersonId, new StringType("Patient/2"), myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("personIdToKeep must have form Person/ where is the id of the person", e.getMessage()); + } + try { + myEmpiProviderR4.mergePersons(new StringType("Person/1"), new StringType("Person/1"), myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("personIdToDelete must be different from personToKeep", e.getMessage()); + } + try { + myEmpiProviderR4.mergePersons(new StringType("Person/abc"), myKeepPersonId, myRequestDetails); + fail(); + } catch (ResourceNotFoundException e) { + assertEquals("Resource Person/abc is not known", e.getMessage()); + } + try { + myEmpiProviderR4.mergePersons(myDeletePersonId, new StringType("Person/abc"), myRequestDetails); + fail(); + } catch (ResourceNotFoundException e) { + assertEquals("Resource Person/abc is not known", e.getMessage()); + } + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderQueryLinkR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderQueryLinkR4Test.java new file mode 100644 index 00000000000..1f547dc9f8f --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderQueryLinkR4Test.java @@ -0,0 +1,98 @@ +package ca.uhn.fhir.jpa.empi.provider; + +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.StringType; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; + +public class EmpiProviderQueryLinkR4Test extends BaseLinkR4Test { +private static final Logger ourLog = LoggerFactory.getLogger(EmpiProviderQueryLinkR4Test.class); + private StringType myLinkSource; + private IdType myPerson1Id; + private IdType myPerson2Id; + + @Before + public void before() { + super.before(); + + // Add a second patient + createPatientAndUpdateLinks(buildJanePatient()); + + // Add a possible duplicate + myLinkSource = new StringType(EmpiLinkSourceEnum.AUTO.name()); + Person person1 = createPerson(); + myPerson1Id = person1.getIdElement().toVersionless(); + Long person1Pid = myIdHelperService.getPidOrNull(person1); + Person person2 = createPerson(); + myPerson2Id = person2.getIdElement().toVersionless(); + Long person2Pid = myIdHelperService.getPidOrNull(person2); + EmpiLink empiLink = new EmpiLink().setPersonPid(person1Pid).setTargetPid(person2Pid).setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE).setLinkSource(EmpiLinkSourceEnum.AUTO); + myEmpiLinkDaoSvc.save(empiLink); + } + + @Test + public void testQueryLinkOneMatch() { + + Parameters result = myEmpiProviderR4.queryLinks(myPersonId, myPatientId, null, null, myRequestDetails); + ourLog.info(myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(result)); + List list = result.getParameter(); + assertThat(list, hasSize(1)); + List part = list.get(0).getPart(); + assertEmpiLink(4, part, myPersonId.getValue(), myPatientId.getValue(), EmpiMatchResultEnum.MATCH); + } + + @Test + public void testQueryLinkThreeMatches() { + // Add a second patient + Patient patient = createPatientAndUpdateLinks(buildJanePatient()); + IdType patientId = patient.getIdElement().toVersionless(); + Person person = getPersonFromTarget(patient); + IdType personId = person.getIdElement().toVersionless(); + + Parameters result = myEmpiProviderR4.queryLinks(null, null, null, myLinkSource, myRequestDetails); + ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(result)); + List list = result.getParameter(); + assertThat(list, hasSize(3)); + List part = list.get(2).getPart(); + assertEmpiLink(4, part, personId.getValue(), patientId.getValue(), EmpiMatchResultEnum.MATCH); + } + + @Test + public void testQueryPossibleDuplicates() { + Parameters result = myEmpiProviderR4.getDuplicatePersons(myRequestDetails); + ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(result)); + List list = result.getParameter(); + assertThat(list, hasSize(1)); + List part = list.get(0).getPart(); + assertEmpiLink(2, part, myPerson1Id.getValue(), myPerson2Id.getValue(), EmpiMatchResultEnum.POSSIBLE_DUPLICATE); + } + + + private void assertEmpiLink(int theExpectedSize, List thePart, String thePersonId, String theTargetId, EmpiMatchResultEnum theMatchResult) { + assertThat(thePart, hasSize(theExpectedSize)); + assertThat(thePart.get(0).getName(), is("personId")); + assertThat(thePart.get(0).getValue().toString(), is(thePersonId)); + assertThat(thePart.get(1).getName(), is("targetId")); + assertThat(thePart.get(1).getValue().toString(), is(theTargetId)); + if (theExpectedSize > 2) { + assertThat(thePart.get(2).getName(), is("matchResult")); + assertThat(thePart.get(2).getValue().toString(), is(theMatchResult.name())); + assertThat(thePart.get(3).getName(), is("linkSource")); + assertThat(thePart.get(3).getValue().toString(), is("AUTO")); + } + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderUpdateLinkR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderUpdateLinkR4Test.java new file mode 100644 index 00000000000..dd7814d2c45 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderUpdateLinkR4Test.java @@ -0,0 +1,92 @@ +package ca.uhn.fhir.jpa.empi.provider; + +import ca.uhn.fhir.empi.api.EmpiConstants; +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.StringType; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class EmpiProviderUpdateLinkR4Test extends BaseLinkR4Test { + + + @Test + public void testUpdateLinkHappyPath() { + myEmpiProviderR4.updateLink(myPersonId, myPatientId, myNoMatch, myRequestDetails); + + myLink = getLink(); + assertEquals(EmpiLinkSourceEnum.MANUAL, myLink.getLinkSource()); + assertEquals(EmpiMatchResultEnum.NO_MATCH, myLink.getMatchResult()); + } + + @Test + public void testUpdateIllegalResultPM() { + try { + myEmpiProviderR4.updateLink(myPersonId, myPatientId, myPossibleMatch, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("$empi-update-link illegal matchResult value 'POSSIBLE_MATCH'. Must be NO_MATCH or MATCH", e.getMessage()); + } + } + + @Test + public void testUpdateIllegalResultPD() { + try { + myEmpiProviderR4.updateLink(myPersonId, myPatientId, myPossibleDuplicate, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("$empi-update-link illegal matchResult value 'POSSIBLE_DUPLICATE'. Must be NO_MATCH or MATCH", e.getMessage()); + } + } + + @Test + public void testUpdateIllegalFirstArg() { + try { + myEmpiProviderR4.updateLink(myPatientId, myPatientId, myNoMatch, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("personId must have form Person/ where is the id of the person", e.getMessage()); + } + } + + @Test + public void testUpdateIllegalSecondArg() { + try { + myEmpiProviderR4.updateLink(myPersonId, myPersonId, myNoMatch, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), endsWith("must have form Patient/ or Practitioner/ where is the id of the resource")); + } + } + + @Test + public void testUpdateStrangePerson() { + Person person = createUnmanagedPerson(); + try { + myEmpiProviderR4.updateLink(new StringType(person.getIdElement().toVersionless().getValue()), myPatientId, myNoMatch, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("Only EMPI Managed Person resources may be updated via this operation. The Person resource provided is not tagged as managed by hapi-empi", e.getMessage()); + } + } + + @Test + public void testExcludedPerson() { + Patient patient = new Patient(); + patient.getMeta().addTag().setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED).setCode(EmpiConstants.CODE_NO_EMPI_MANAGED); + createPatient(patient); + try { + myEmpiProviderR4.updateLink(myPersonId, new StringType(patient.getIdElement().toVersionless().getValue()), myNoMatch, myRequestDetails); + fail(); + } catch (InvalidRequestException e) { + assertEquals("The target is marked with the " + EmpiConstants.CODE_NO_EMPI_MANAGED + " tag which means it may not be EMPI linked.", e.getMessage()); + } + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/searchparam/SearchParameterTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/searchparam/SearchParameterTest.java new file mode 100644 index 00000000000..269d73f4d50 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/searchparam/SearchParameterTest.java @@ -0,0 +1,64 @@ +package ca.uhn.fhir.jpa.empi.searchparam; + +import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test; +import ca.uhn.fhir.jpa.empi.config.EmpiSearchParameterLoader; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.TokenParam; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class SearchParameterTest extends BaseEmpiR4Test { + private static final Logger ourLog = LoggerFactory.getLogger(SearchParameterTest.class); + @Autowired + EmpiSearchParameterLoader myEmpiSearchParameterLoader; + @Autowired + SearchParamRegistryImpl mySearchParamRegistry; + + @Before + public void before() { + myEmpiSearchParameterLoader.daoUpdateEmpiSearchParameters(); + mySearchParamRegistry.forceRefresh(); + } + + @Test + public void testCanFindPossibleMatches() { + // Create a possible match + Patient patient = buildJanePatient(); + patient.getNameFirstRep().setFamily("familyone"); + patient = createPatientAndUpdateLinks(patient); + + Patient patient2 = buildJanePatient(); + patient2.getNameFirstRep().setFamily("pleasedonotmatchatall"); + patient2 = createPatientAndUpdateLinks(patient2); + + assertThat(patient2, is(possibleMatchWith(patient))); + // Now confirm we can find it using our custom search parameter + + SearchParameterMap map = new SearchParameterMap(); + map.setLoadSynchronous(true); + map.add("assurance", new TokenParam(Person.IdentityAssuranceLevel.LEVEL2.toCode())); + IBundleProvider result = myPersonDao.search(map); + + assertEquals(1, result.size().intValue()); + Person person = (Person) result.getResources(0, 1).get(0); + String encoded = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(person); + ourLog.info("Search result: {}", encoded); + List links = person.getLink(); + assertEquals(2, links.size()); + assertEquals(Person.IdentityAssuranceLevel.LEVEL3, links.get(0).getAssurance()); + assertEquals(Person.IdentityAssuranceLevel.LEVEL2, links.get(1).getAssurance()); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvcTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvcTest.java new file mode 100644 index 00000000000..edf6b38fb75 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvcTest.java @@ -0,0 +1,67 @@ +package ca.uhn.fhir.jpa.empi.svc; + +import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Practitioner; +import org.hl7.fhir.r4.model.Reference; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.junit.Assert.assertEquals; + +public class EmpiCandidateSearchSvcTest extends BaseEmpiR4Test { + + @Autowired + EmpiCandidateSearchSvc myEmpiCandidateSearchSvc; + + @Test + public void testFindCandidates() { + Patient jane = buildJanePatient(); + jane.setActive(true); + Patient createdJane = createPatient(jane); + Patient newJane = buildJanePatient(); + + Collection result = myEmpiCandidateSearchSvc.findCandidates("Patient", newJane); + assertEquals(1, result.size()); + } + + + @Test + public void findCandidatesMultipleMatchesDoNotCauseDuplicates() { + Date today = new Date(); + Patient jane = buildJaneWithBirthday(today); + + jane.setActive(true); + createPatient(jane); + + Patient newJane = buildJaneWithBirthday(today); + + Collection result = myEmpiCandidateSearchSvc.findCandidates("Patient", newJane); + assertEquals(1, result.size()); + } + + @Test + public void testFindCandidatesCorrectlySearchesWithReferenceParams() { + Practitioner practitioner = new Practitioner(); + Practitioner practitionerAndUpdateLinks = createPractitionerAndUpdateLinks(practitioner); + + Patient managedPatient = new Patient(); + managedPatient.setActive(true); + managedPatient.setGeneralPractitioner(Collections.singletonList(new Reference(practitionerAndUpdateLinks.getId()))); + createPatient(managedPatient); + + Patient incomingPatient = new Patient(); + incomingPatient.setActive(true); + incomingPatient.setGeneralPractitioner(Collections.singletonList(new Reference(practitionerAndUpdateLinks.getId()))); + + Collection patient = myEmpiCandidateSearchSvc.findCandidates("Patient", incomingPatient); + assertThat(patient, hasSize(1)); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcTest.java new file mode 100644 index 00000000000..c3b3a22f104 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcTest.java @@ -0,0 +1,104 @@ +package ca.uhn.fhir.jpa.empi.svc; + +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiLinkSvc; +import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class EmpiLinkSvcTest extends BaseEmpiR4Test { + @Autowired + IEmpiLinkSvc myEmpiLinkSvc; + + @After + public void after() { + myExpungeEverythingService.expungeEverythingByType(EmpiLink.class); + super.after(); + } + @Test + public void compareEmptyPatients() { + Patient patient = new Patient(); + patient.setId("Patient/1"); + EmpiMatchResultEnum result = myEmpiResourceComparatorSvc.getMatchResult(patient, patient); + assertEquals(EmpiMatchResultEnum.NO_MATCH, result); + } + + @Test + public void testCreateRemoveLink() { + assertLinkCount(0); + Person person = createPerson(); + IdType personId = person.getIdElement().toUnqualifiedVersionless(); + assertEquals(0, person.getLink().size()); + Patient patient = createPatient(); + + { + myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.POSSIBLE_MATCH, EmpiLinkSourceEnum.AUTO, createContextForCreate()); + assertLinkCount(1); + Person newPerson = myPersonDao.read(personId); + assertEquals(1, newPerson.getLink().size()); + } + + { + myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate()); + assertLinkCount(1); + Person newPerson = myPersonDao.read(personId); + assertEquals(0, newPerson.getLink().size()); + } + } + + @Test + public void testManualEmpiLinksCannotBeModifiedBySystem() { + Person person = createPerson(buildJanePerson()); + Patient patient = createPatient(buildJanePatient()); + + myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate()); + try { + myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, null); + fail(); + } catch (InternalErrorException e) { + assertThat(e.getMessage(), is(equalTo("EMPI system is not allowed to modify links on manually created links"))); + } + } + + @Test + public void testAutomaticallyAddedNO_MATCHEmpiLinksAreNotAllowed() { + Person person = createPerson(buildJanePerson()); + Patient patient = createPatient(buildJanePatient()); + + // Test: it should be impossible to have a AUTO NO_MATCH record. The only NO_MATCH records in the system must be MANUAL. + try { + myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.AUTO, null); + fail(); + } catch (InternalErrorException e) { + assertThat(e.getMessage(), is(equalTo("EMPI system is not allowed to automatically NO_MATCH a resource"))); + } + } + + @Test + public void testSyncDoesNotSyncNoMatchLinks() { + Person person = createPerson(buildJanePerson()); + Patient patient1 = createPatient(buildJanePatient()); + Patient patient2 = createPatient(buildJanePatient()); + assertEquals(0, myEmpiLinkDao.count()); + + myEmpiLinkDaoSvc.createOrUpdateLinkEntity(person, patient1, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate()); + myEmpiLinkDaoSvc.createOrUpdateLinkEntity(person, patient2, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate()); + myEmpiLinkSvc.syncEmpiLinksToPersonLinks(person, createContextForCreate()); + assertTrue(person.hasLink()); + assertEquals(patient1.getIdElement().toVersionless().getValue(), person.getLinkFirstRep().getTarget().getReference()); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcMultipleEidModeTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcMultipleEidModeTest.java new file mode 100644 index 00000000000..04020c2bb57 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcMultipleEidModeTest.java @@ -0,0 +1,179 @@ +package ca.uhn.fhir.jpa.empi.svc; + +import ca.uhn.fhir.empi.api.EmpiConstants; +import ca.uhn.fhir.empi.api.IEmpiLinkSvc; +import ca.uhn.fhir.empi.model.CanonicalEID; +import ca.uhn.fhir.empi.util.EIDHelper; +import ca.uhn.fhir.empi.util.PersonHelper; +import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.junit.Test; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.TestPropertySource; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.in; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; +import static org.slf4j.LoggerFactory.getLogger; + +@TestPropertySource(properties = { + "empi.prevent_multiple_eids=false" +}) +public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test { + private static final Logger ourLog = getLogger(EmpiMatchLinkSvcMultipleEidModeTest.class); + @Autowired + IEmpiLinkSvc myEmpiLinkSvc; + @Autowired + private EIDHelper myEidHelper; + @Autowired + private PersonHelper myPersonHelper; + + + @Test + public void testIncomingPatientWithEIDThatMatchesPersonWithHapiEidAddsExternalEidsToPerson() { + // Existing Person with system-assigned EID found linked from matched Patient. incoming Patient has EID. Replace Person system-assigned EID with Patient EID. + Patient patient = createPatientAndUpdateLinks(buildJanePatient()); + + Person janePerson = getPersonFromTarget(patient); + List hapiEid = myEidHelper.getHapiEid(janePerson); + String foundHapiEid = hapiEid.get(0).getValue(); + + Patient janePatient = buildJanePatient(); + addExternalEID(janePatient, "12345"); + addExternalEID(janePatient, "67890"); + createPatientAndUpdateLinks(janePatient); + + //We want to make sure the patients were linked to the same person. + assertThat(patient, is(samePersonAs(janePatient))); + + Person person = getPersonFromTarget(patient); + + List identifier = person.getIdentifier(); + + //The collision should have kept the old identifier + Identifier firstIdentifier = identifier.get(0); + assertThat(firstIdentifier.getSystem(), is(equalTo(EmpiConstants.HAPI_ENTERPRISE_IDENTIFIER_SYSTEM))); + assertThat(firstIdentifier.getValue(), is(equalTo(foundHapiEid))); + + //The collision should have added a new identifier with the external system. + Identifier secondIdentifier = identifier.get(1); + assertThat(secondIdentifier.getSystem(), is(equalTo(myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem()))); + assertThat(secondIdentifier.getValue(), is(equalTo("12345"))); + + Identifier thirdIdentifier = identifier.get(2); + assertThat(thirdIdentifier.getSystem(), is(equalTo(myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem()))); + assertThat(thirdIdentifier.getValue(), is(equalTo("67890"))); + } + + + @Test + // Test Case #4 + public void testHavingMultipleEIDsOnIncomingPatientMatchesCorrectly() { + + Patient patient1 = buildJanePatient(); + addExternalEID(patient1, "id_1"); + addExternalEID(patient1, "id_2"); + addExternalEID(patient1, "id_3"); + addExternalEID(patient1, "id_4"); + createPatientAndUpdateLinks(patient1); + + Patient patient2 = buildPaulPatient(); + addExternalEID(patient2, "id_5"); + addExternalEID(patient2, "id_1"); + patient2 = createPatientAndUpdateLinks(patient2); + + assertThat(patient1, is(samePersonAs(patient2))); + + clearExternalEIDs(patient2); + addExternalEID(patient2, "id_6"); + + //At this point, there should be 5 EIDs on the person + Person personFromTarget = getPersonFromTarget(patient2); + assertThat(personFromTarget.getIdentifier(), hasSize(5)); + + updatePatientAndUpdateLinks(patient2); + + assertThat(patient1, is(samePersonAs(patient2))); + + + personFromTarget = getPersonFromTarget(patient2); + assertThat(personFromTarget.getIdentifier(), hasSize(6)); + + } + + @Test + public void testDuplicatePersonLinkIsCreatedWhenAnIncomingPatientArrivesWithEIDThatMatchesAnotherEIDPatient() { + + Patient patient1 = buildJanePatient(); + addExternalEID(patient1, "eid-1"); + addExternalEID(patient1, "eid-11"); + patient1 = createPatientAndUpdateLinks(patient1); + + Patient patient2 = buildJanePatient(); + addExternalEID(patient2, "eid-2"); + addExternalEID(patient2, "eid-22"); + patient2 = createPatientAndUpdateLinks(patient2); + + List possibleDuplicates = myEmpiLinkDaoSvc.getPossibleDuplicates(); + assertThat(possibleDuplicates, hasSize(1)); + + + List duplicatePids = Stream.of(patient1, patient2) + .map(this::getPersonFromTarget) + .map(myIdHelperService::getPidOrNull) + .collect(Collectors.toList()); + + //The two Persons related to the patients should both show up in the only existing POSSIBLE_DUPLICATE EmpiLink. + EmpiLink empiLink = possibleDuplicates.get(0); + assertThat(empiLink.getPersonPid(), is(in(duplicatePids))); + assertThat(empiLink.getTargetPid(), is(in(duplicatePids))); + } + + @Test + // Test Case #5 + public void testWhenPatientEidUpdateWouldCauseALinkChangeThatDuplicatePersonIsCreatedInstead() { + Patient patient1 = buildJanePatient(); + addExternalEID(patient1, "eid-1"); + addExternalEID(patient1, "eid-11"); + patient1 = createPatientAndUpdateLinks(patient1); + + Patient patient2 = buildPaulPatient(); + addExternalEID(patient2, "eid-2"); + addExternalEID(patient2, "eid-22"); + patient2 = createPatientAndUpdateLinks(patient2); + + Patient patient3 = buildPaulPatient(); + addExternalEID(patient3, "eid-22"); + patient3 = createPatientAndUpdateLinks(patient3); + + //Now, Patient 2 and 3 are linked, and the person has 2 eids. + assertThat(patient2, is(samePersonAs(patient3))); + + //Now lets change one of the EIDs on an incoming patient to one that matches our original patient. + //This should create a situation in which the incoming EIDs are matched to _two_ unique patients. In this case, we want to + //set them all to possible_match, and set the two persons as possible duplicates. + patient2.getIdentifier().clear(); + addExternalEID(patient2, "eid-11"); + addExternalEID(patient2, "eid-22"); + patient2 = updatePatientAndUpdateLinks(patient2); + + assertThat(patient2, is(not(matchedToAPerson()))); + assertThat(patient2,is(possibleMatchWith(patient1))); + assertThat(patient2,is(possibleMatchWith(patient3))); + + List possibleDuplicates = myEmpiLinkDaoSvc.getPossibleDuplicates(); + assertThat(possibleDuplicates, hasSize(1)); + assertThat(patient3, is(possibleDuplicateOf(patient1))); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcTest.java new file mode 100644 index 00000000000..f9b70ba31f6 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcTest.java @@ -0,0 +1,543 @@ +package ca.uhn.fhir.jpa.empi.svc; + +import ca.uhn.fhir.empi.api.EmpiConstants; +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiLinkSvc; +import ca.uhn.fhir.empi.model.CanonicalEID; +import ca.uhn.fhir.empi.util.EIDHelper; +import ca.uhn.fhir.empi.util.PersonHelper; +import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.HumanName; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.Practitioner; +import org.junit.Test; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.Matchers.blankOrNullString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.equalToIgnoringCase; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.in; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; +import static org.slf4j.LoggerFactory.getLogger; + +public class EmpiMatchLinkSvcTest extends BaseEmpiR4Test { + private static final Logger ourLog = getLogger(EmpiMatchLinkSvcTest.class); + @Autowired + IEmpiLinkSvc myEmpiLinkSvc; + @Autowired + private EIDHelper myEidHelper; + @Autowired + private PersonHelper myPersonHelper; + + @Test + public void testAddPatientLinksToNewPersonIfNoneFound() { + createPatientAndUpdateLinks(buildJanePatient()); + assertLinkCount(1); + } + + @Test + public void testAddPatientLinksToNewPersonIfNoMatch() { + Patient patient1 = createPatientAndUpdateLinks(buildJanePatient()); + Patient patient2 = createPatientAndUpdateLinks(buildPaulPatient()); + + assertLinkCount(2); + assertThat(patient1, is(not(samePersonAs(patient2)))); + } + + @Test + public void testAddPatientLinksToExistingPersonIfMatch() { + Patient patient1 = createPatientAndUpdateLinks(buildJanePatient()); + assertLinkCount(1); + + Patient patient2 = createPatientAndUpdateLinks(buildJanePatient()); + assertLinkCount(2); + + assertThat(patient1, is(samePersonAs(patient2))); + } + + @Test + public void testWhenMatchOccursOnPersonThatHasBeenManuallyNOMATCHedThatItIsBlocked() { + Patient originalJane = createPatientAndUpdateLinks(buildJanePatient()); + IBundleProvider search = myPersonDao.search(new SearchParameterMap()); + IAnyResource janePerson = (IAnyResource) search.getResources(0,1).get(0); + + //Create a manual NO_MATCH between janePerson and unmatchedJane. + Patient unmatchedJane= createPatient(buildJanePatient()); + myEmpiLinkSvc.updateLink(janePerson, unmatchedJane, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate()); + + //rerun EMPI rules against unmatchedJane. + myEmpiMatchLinkSvc.updateEmpiLinksForEmpiTarget(unmatchedJane, createContextForCreate()); + + assertThat(unmatchedJane, is(not(samePersonAs(janePerson)))); + assertThat(unmatchedJane, is(not(linkedTo(originalJane)))); + } + + @Test + public void testWhenPOSSIBLE_MATCHOccursOnPersonThatHasBeenManuallyNOMATCHedThatItIsBlocked() { + Patient originalJane = createPatientAndUpdateLinks(buildJanePatient()); + IBundleProvider search = myPersonDao.search(new SearchParameterMap()); + IAnyResource janePerson = (IAnyResource) search.getResources(0, 1).get(0); + + Patient unmatchedPatient = createPatient(buildJanePatient()); + + //This simulates an admin specifically saying that unmatchedPatient does NOT match janePerson. + myEmpiLinkSvc.updateLink(janePerson, unmatchedPatient, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate()); + //TODO change this so that it will only partially match. + + //Now normally, when we run update links, it should link to janePerson. However, this manual NO_MATCH link + //should cause a whole new Person to be created. + myEmpiMatchLinkSvc.updateEmpiLinksForEmpiTarget(unmatchedPatient, createContextForCreate()); + + assertThat(unmatchedPatient, is(not(samePersonAs(janePerson)))); + assertThat(unmatchedPatient, is(not(linkedTo(originalJane)))); + } + + @Test + public void testWhenPatientIsCreatedWithEIDThatItPropagatesToNewPerson() { + String sampleEID = "sample-eid"; + Patient janePatient = addExternalEID(buildJanePatient(), sampleEID); + janePatient = createPatientAndUpdateLinks(janePatient); + + Optional empiLink = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(janePatient.getIdElement().getIdPartAsLong()); + assertThat(empiLink.isPresent(), is(true)); + + Person person = getPersonFromEmpiLink(empiLink.get()); + List externalEid = myEidHelper.getExternalEid(person); + + assertThat(externalEid.get(0).getSystem(), is(equalTo(myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem()))); + assertThat(externalEid.get(0).getValue(), is(equalTo(sampleEID))); + } + + @Test + public void testWhenPatientIsCreatedWithoutAnEIDThePersonGetsAutomaticallyAssignedOne() { + Patient patient = createPatientAndUpdateLinks(buildJanePatient()); + EmpiLink empiLink = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(patient.getIdElement().getIdPartAsLong()).get(); + + Person person = getPersonFromEmpiLink(empiLink); + Identifier identifierFirstRep = person.getIdentifierFirstRep(); + assertThat(identifierFirstRep.getSystem(), is(equalTo(EmpiConstants.HAPI_ENTERPRISE_IDENTIFIER_SYSTEM))); + assertThat(identifierFirstRep.getValue(), not(blankOrNullString())); + } + + @Test + public void testPatientAttributesAreCopiedOverWhenPersonIsCreatedFromPatient() { + Patient patient = createPatientAndUpdateLinks(buildPatientWithNameIdAndBirthday("Gary", "GARY_ID", new Date())); + + Optional empiLink = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(patient.getIdElement().getIdPartAsLong()); + Person read = getPersonFromEmpiLink(empiLink.get()); + + assertThat(read.getNameFirstRep().getFamily(), is(equalTo(patient.getNameFirstRep().getFamily()))); + assertThat(read.getNameFirstRep().getGivenAsSingleString(), is(equalTo(patient.getNameFirstRep().getGivenAsSingleString()))); + assertThat(read.getBirthDateElement().toHumanDisplay(), is(equalTo(patient.getBirthDateElement().toHumanDisplay()))); + assertThat(read.getTelecomFirstRep().getValue(), is(equalTo(patient.getTelecomFirstRep().getValue()))); + assertThat(read.getPhoto().getData(), is(equalTo(patient.getPhotoFirstRep().getData()))); + assertThat(read.getGender(), is(equalTo(patient.getGender()))); + } + + @Test + public void testPatientMatchingAnotherPatientLinksToSamePerson() { + Patient janePatient = createPatientAndUpdateLinks(buildJanePatient()); + Patient sameJanePatient = createPatientAndUpdateLinks(buildJanePatient()); + assertThat(janePatient, is(samePersonAs(sameJanePatient))); + } + + @Test + public void testIncomingPatientWithEIDThatMatchesPersonWithHapiEidAddsExternalEidToPerson(){ + // Existing Person with system-assigned EID found linked from matched Patient. incoming Patient has EID. Replace Person system-assigned EID with Patient EID. + Patient patient = createPatientAndUpdateLinks(buildJanePatient()); + + Person janePerson = getPersonFromTarget(patient); + List hapiEid = myEidHelper.getHapiEid(janePerson); + String foundHapiEid = hapiEid.get(0).getValue(); + + Patient janePatient= addExternalEID(buildJanePatient(), "12345"); + createPatientAndUpdateLinks(janePatient); + + //We want to make sure the patients were linked to the same person. + assertThat(patient, is(samePersonAs(janePatient))); + + Person person = getPersonFromTarget(patient); + + List identifier = person.getIdentifier(); + + //The collision should have kept the old identifier + Identifier firstIdentifier = identifier.get(0); + assertThat(firstIdentifier.getSystem(), is(equalTo(EmpiConstants.HAPI_ENTERPRISE_IDENTIFIER_SYSTEM))); + assertThat(firstIdentifier.getValue(), is(equalTo(foundHapiEid))); + + //The collision should have added a new identifier with the external system. + Identifier secondIdentifier = identifier.get(1); + assertThat(secondIdentifier.getSystem(), is(equalTo(myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem()))); + assertThat(secondIdentifier.getValue(), is(equalTo("12345"))); + } + + @Test + public void testIncomingPatientWithEidMatchesAnotherPatientWithSameEIDAreLinked() { + Patient patient1 = addExternalEID(buildJanePatient(), "uniqueid"); + createPatientAndUpdateLinks(patient1); + + Patient patient2 = addExternalEID(buildPaulPatient(), "uniqueid"); + createPatientAndUpdateLinks(patient2); + + assertThat(patient1, is(samePersonAs(patient2))); + } + @Test + public void testHavingMultipleEIDsOnIncomingPatientMatchesCorrectly() { + + Patient patient1 = buildJanePatient(); + addExternalEID(patient1, "id_1"); + addExternalEID(patient1, "id_2"); + addExternalEID(patient1, "id_3"); + addExternalEID(patient1, "id_4"); + createPatientAndUpdateLinks(patient1); + + Patient patient2 = buildPaulPatient(); + addExternalEID(patient2, "id_5"); + addExternalEID(patient2, "id_1"); + createPatientAndUpdateLinks(patient2); + + assertThat(patient1, is(samePersonAs(patient2))); + } + + @Test + public void testDuplicatePersonLinkIsCreatedWhenAnIncomingPatientArrivesWithEIDThatMatchesAnotherEIDPatient() { + + Patient patient1 = addExternalEID(buildJanePatient(), "eid-1"); + patient1 = createPatientAndUpdateLinks(patient1); + + Patient patient2 = addExternalEID(buildJanePatient(), "eid-2"); + patient2 = createPatientAndUpdateLinks(patient2); + + List possibleDuplicates = myEmpiLinkDaoSvc.getPossibleDuplicates(); + assertThat(possibleDuplicates, hasSize(1)); + + + List duplicatePids = Stream.of(patient1, patient2) + .map(this::getPersonFromTarget) + .map(myIdHelperService::getPidOrNull) + .collect(Collectors.toList()); + + //The two Persons related to the patients should both show up in the only existing POSSIBLE_DUPLICATE EmpiLink. + EmpiLink empiLink = possibleDuplicates.get(0); + assertThat(empiLink.getPersonPid(), is(in(duplicatePids))); + assertThat(empiLink.getTargetPid(), is(in(duplicatePids))); + } + + @Test + public void testPatientWithNoEmpiTagIsNotMatched() { + // Patient with "no-empi" tag is not matched + Patient janePatient = buildJanePatient(); + janePatient.getMeta().addTag(EmpiConstants.SYSTEM_EMPI_MANAGED, EmpiConstants.CODE_NO_EMPI_MANAGED, "Don't EMPI on me!"); + createPatientAndUpdateLinks(janePatient); + assertLinkCount(0); + } + + @Test + public void testPractitionersDoNotMatchToPatients() { + Patient janePatient = createPatientAndUpdateLinks(buildJanePatient()); + Practitioner janePractitioner = createPractitionerAndUpdateLinks(buildJanePractitioner()); + + assertLinkCount(2); + assertThat(janePatient, is(not(samePersonAs(janePractitioner)))); + } + + @Test + public void testPractitionersThatMatchShouldLink() { + Practitioner janePractitioner = createPractitionerAndUpdateLinks(buildJanePractitioner()); + Practitioner anotherJanePractitioner = createPractitionerAndUpdateLinks(buildJanePractitioner()); + + assertLinkCount(2); + assertThat(anotherJanePractitioner, is(samePersonAs(janePractitioner))); + } + + @Test + public void testWhenThereAreNoMATCHOrPOSSIBLE_MATCHOutcomesThatANewPersonIsCreated(){ + /** + * CASE 1: No MATCHED and no PROBABLE_MATCHED outcomes -> a new Person resource + * is created and linked to that Pat/Prac. + */ + assertLinkCount(0); + Patient janePatient = createPatientAndUpdateLinks(buildJanePatient()); + assertLinkCount(1); + assertThat(janePatient, is(matchedToAPerson())); + } + + @Test + public void testWhenAllMATCHResultsAreToSamePersonThatTheyAreLinked(){ + /** + * CASE 2: All of the MATCHED Pat/Prac resources are already linked to the same Person -> + * a new Link is created between the new Pat/Prac and that Person and is set to MATCHED. + */ + Patient janePatient = createPatientAndUpdateLinks(buildJanePatient()); + Patient janePatient2 = createPatientAndUpdateLinks(buildJanePatient()); + + assertLinkCount(2); + assertThat(janePatient, is(samePersonAs(janePatient2))); + + Patient incomingJanePatient = createPatientAndUpdateLinks(buildJanePatient()); + assertThat(incomingJanePatient, is(samePersonAs(janePatient, janePatient2))); + assertThat(incomingJanePatient, is(linkedTo(janePatient, janePatient2))); + } + + @Test + public void testMATCHResultWithMultipleCandidatesCreatesPOSSIBLE_DUPLICATELinksAndNoPersonIsCreated(){ + /** + * CASE 3: The MATCHED Pat/Prac resources link to more than one Person -> Mark all links as POSSIBLE_MATCH. + * All other Person resources are marked as POSSIBLE_DUPLICATE of this first Person. + */ + Patient janePatient = createPatientAndUpdateLinks(buildJanePatient()); + + Patient janePatient2 = createPatient(buildJanePatient()); + + //In a normal situation, janePatient2 would just match to jane patient, but here we need to hack it so they are their + //own individual Persons for the purpose of this test. + IAnyResource person = myPersonHelper.createPersonFromEmpiTarget(janePatient2); + myEmpiLinkSvc.updateLink(person, janePatient2, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, createContextForCreate()); + assertThat(janePatient, is(not(samePersonAs(janePatient2)))); + + //In theory, this will match both Persons! + Patient incomingJanePatient = createPatientAndUpdateLinks(buildJanePatient()); + + //There should now be a single POSSIBLE_DUPLICATE link with + assertThat(janePatient, is(possibleDuplicateOf(janePatient2))); + + //There should now be 2 POSSIBLE_MATCH links with this person. + assertThat(incomingJanePatient, is(possibleMatchWith(janePatient, janePatient2))); + + //Ensure there is no successful MATCH links for incomingJanePatient + Optional matchedLinkForTargetPid = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(incomingJanePatient)); + assertThat(matchedLinkForTargetPid.isPresent(), is(false)); + } + + @Test + public void testWhenAllMatchResultsArePOSSIBLE_MATCHThattheyAreLinkedAndNoPersonIsCreated(){ + /** + * CASE 4: Only POSSIBLE_MATCH outcomes -> In this case, empi-link records are created with POSSIBLE_MATCH + * outcome and await manual assignment to either NO_MATCH or MATCHED. Person resources are not changed. + */ + Patient patient = buildJanePatient(); + patient.getNameFirstRep().setFamily("familyone"); + patient = createPatientAndUpdateLinks(patient); + assertThat(patient, is(samePersonAs(patient))); + + Patient patient2 = buildJanePatient(); + patient2.getNameFirstRep().setFamily("pleasedonotmatchatall"); + patient2 = createPatientAndUpdateLinks(patient2); + + assertThat(patient2, is(possibleMatchWith(patient))); + + Patient patient3 = buildJanePatient(); + patient3.getNameFirstRep().setFamily("pleasedonotmatchatall"); + patient3 = createPatientAndUpdateLinks(patient3); + + assertThat(patient3, is(possibleMatchWith(patient2))); + assertThat(patient3, is(possibleMatchWith(patient))); + } + + @Test + public void testWhenAnIncomingResourceHasMatchesAndPossibleMatchesThatItLinksToMatch() { + Patient patient = buildJanePatient(); + patient.getNameFirstRep().setFamily("familyone"); + patient = createPatientAndUpdateLinks(patient); + assertThat(patient, is(samePersonAs(patient))); + + Patient patient2 = buildJanePatient(); + patient2.getNameFirstRep().setFamily("pleasedonotmatchatall"); + patient2 = createPatientAndUpdateLinks(patient2); + + Patient patient3 = buildJanePatient(); + patient3.getNameFirstRep().setFamily("familyone"); + patient3 = createPatientAndUpdateLinks(patient3); + + assertThat(patient2, is(not(samePersonAs(patient)))); + assertThat(patient2, is(possibleMatchWith(patient))); + assertThat(patient3, is(samePersonAs(patient))); + } + + @Test + public void testAutoMatchesGenerateAssuranceLevel3() { + Patient patient = createPatientAndUpdateLinks(buildJanePatient()); + Person janePerson = getPersonFromTarget(patient); + Person.PersonLinkComponent linkFirstRep = janePerson.getLinkFirstRep(); + + assertThat(linkFirstRep.getTarget().getReference(), is(equalTo(patient.getIdElement().toVersionless().toString()))); + assertThat(linkFirstRep.getAssurance(), is(equalTo(Person.IdentityAssuranceLevel.LEVEL3))); + } + + @Test + public void testManualMatchesGenerateAssuranceLevel4() { + Patient patient = createPatientAndUpdateLinks(buildJanePatient()); + Person janePerson = getPersonFromTarget(patient); + myEmpiLinkSvc.updateLink(janePerson, patient, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate()); + + janePerson = getPersonFromTarget(patient); + Person.PersonLinkComponent linkFirstRep = janePerson.getLinkFirstRep(); + + assertThat(linkFirstRep.getTarget().getReference(), is(equalTo(patient.getIdElement().toVersionless().toString()))); + assertThat(linkFirstRep.getAssurance(), is(equalTo(Person.IdentityAssuranceLevel.LEVEL4))); + } + + //Case #1 + @Test + public void testPatientUpdateOverwritesPersonDataOnChanges() { + Patient janePatient= createPatientAndUpdateLinks(buildJanePatient()); + Person janePerson = getPersonFromTarget(janePatient); + + //Change Jane's name to paul. + Patient patient1 = buildPaulPatient(); + patient1.setId(janePatient.getId()); + Patient janePaulPatient = updatePatientAndUpdateLinks(patient1); + + assertThat(janePerson, is(samePersonAs(janePaulPatient))); + + //Ensure the related person was updated with new info. + Person personFromTarget = getPersonFromTarget(janePaulPatient); + HumanName nameFirstRep = personFromTarget.getNameFirstRep(); + assertThat(nameFirstRep.getGivenAsSingleString(), is(equalToIgnoringCase("paul"))); + } + + @Test + public void testPatientCreateDoesNotOverwritePersonAttributesThatAreInvolvedInLinking() { + Patient paul = buildPaulPatient(); + paul.setGender(Enumerations.AdministrativeGender.MALE); + paul = createPatientAndUpdateLinks(paul); + + Person personFromTarget = getPersonFromTarget(paul); + assertThat(personFromTarget.getGender(), is(equalTo(Enumerations.AdministrativeGender.MALE))); + + Patient paul2 = buildPaulPatient(); + paul2.setGender(Enumerations.AdministrativeGender.FEMALE); + paul2 = createPatientAndUpdateLinks(paul2); + + assertThat(paul2, is(samePersonAs(paul))); + + //Newly matched patients aren't allowed to overwrite Person Attributes unless they are empty, so gender should still be set to male. + Person paul2Person= getPersonFromTarget(paul2); + assertThat(paul2Person.getGender(), is(equalTo(Enumerations.AdministrativeGender.MALE))); + } + + @Test + //Test Case #1 + public void testPatientUpdatesOverwritePersonData() { + Patient paul = buildPaulPatient(); + String incorrectBirthdate = "1980-06-27"; + paul.getBirthDateElement().setValueAsString(incorrectBirthdate); + paul = createPatientAndUpdateLinks(paul); + + Person personFromTarget = getPersonFromTarget(paul); + assertThat(personFromTarget.getBirthDateElement().getValueAsString(), is(incorrectBirthdate)); + + String correctBirthdate = "1990-06-28"; + paul.getBirthDateElement().setValueAsString(correctBirthdate); + + paul = updatePatientAndUpdateLinks(paul); + + personFromTarget = getPersonFromTarget(paul); + assertThat(personFromTarget.getBirthDateElement().getValueAsString(), is(equalTo(correctBirthdate))); + assertLinkCount(1); + } + + @Test + // Test Case #3 + public void testUpdatedEidThatWouldRelinkAlsoCausesPossibleDuplicate() { + String EID_1 = "123"; + String EID_2 = "456"; + + Patient paul = createPatientAndUpdateLinks(addExternalEID(buildPaulPatient(), EID_1)); + Person originalPaulPerson = getPersonFromTarget(paul); + + Patient jane = createPatientAndUpdateLinks(addExternalEID(buildJanePatient(), EID_2)); + Person originalJanePerson = getPersonFromTarget(jane); + + clearExternalEIDs(paul); + addExternalEID(paul, EID_2); + updatePatientAndUpdateLinks(paul); + + assertThat(originalJanePerson, is(possibleDuplicateOf(originalPaulPerson))); + assertThat(jane, is(samePersonAs(paul))); + } + + @Test + //Test Case #2 + public void testSinglyLinkedPersonThatGetsAnUpdatedEidSimplyUpdatesEID() { + String EID_1 = "123"; + String EID_2 = "456"; + + Patient paul = createPatientAndUpdateLinks(addExternalEID(buildPaulPatient(), EID_1)); + Person originalPaulPerson = getPersonFromTarget(paul); + String oldEid = myEidHelper.getExternalEid(originalPaulPerson).get(0).getValue(); + assertThat(oldEid, is(equalTo(EID_1))); + + clearExternalEIDs(paul); + addExternalEID(paul, EID_2); + + paul = updatePatientAndUpdateLinks(paul); + + assertNoDuplicates(); + + Person newlyFoundPaulPerson = getPersonFromTarget(paul); + assertThat(originalPaulPerson, is(samePersonAs(newlyFoundPaulPerson))); + String newEid = myEidHelper.getExternalEid(newlyFoundPaulPerson).get(0).getValue(); + assertThat(newEid, is(equalTo(EID_2))); + } + + private void assertNoDuplicates() { + List possibleDuplicates = myEmpiLinkDaoSvc.getPossibleDuplicates(); + assertThat(possibleDuplicates, hasSize(0)); + } + + @Test + //Test Case #3 + public void testWhenAnEidChangeWouldCauseARelinkingThatAPossibleDuplicateIsCreated() { + Patient patient1 = buildJanePatient(); + addExternalEID(patient1, "eid-1"); + patient1 = createPatientAndUpdateLinks(patient1); + + Patient patient2 = buildPaulPatient(); + addExternalEID(patient2, "eid-2"); + patient2 = createPatientAndUpdateLinks(patient2); + + Patient patient3 = buildPaulPatient(); + addExternalEID(patient3, "eid-2"); + patient3 = createPatientAndUpdateLinks(patient3); + + //Now, Patient 2 and 3 are linked, and the person has 2 eids. + assertThat(patient2, is(samePersonAs(patient3))); + assertNoDuplicates(); + // Person A -> {P1} + // Person B -> {P2, P3} + + patient2.getIdentifier().clear(); + addExternalEID(patient2, "eid-1"); + patient2 = updatePatientAndUpdateLinks(patient2); + + // Person A -> {P1, P2} + // Person B -> {P3} + // Possible duplicates A<->B + + assertThat(patient2, is(samePersonAs(patient1))); + + List possibleDuplicates = myEmpiLinkDaoSvc.getPossibleDuplicates(); + assertThat(possibleDuplicates, hasSize(1)); + assertThat(patient3, is(possibleDuplicateOf(patient1))); + + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcTest.java new file mode 100644 index 00000000000..8c512b43940 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcTest.java @@ -0,0 +1,336 @@ +package ca.uhn.fhir.jpa.empi.svc; + +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc; +import ca.uhn.fhir.empi.model.EmpiTransactionContext; +import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test; +import ca.uhn.fhir.jpa.empi.helper.EmpiLinkHelper; +import ca.uhn.fhir.jpa.empi.interceptor.IEmpiStorageInterceptor; +import ca.uhn.fhir.jpa.entity.EmpiLink; +import ca.uhn.fhir.rest.server.TransactionLogMessages; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import org.hl7.fhir.r4.model.Address; +import org.hl7.fhir.r4.model.DateType; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.HumanName; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.Reference; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test { + public static final String GIVEN_NAME = "Jenn"; + public static final String FAMILY_NAME = "Chan"; + public static final String POSTAL_CODE = "M6G 1B4"; + private static final String BAD_GIVEN_NAME = "Bob"; + + @Autowired + IEmpiPersonMergerSvc myEmpiPersonMergerSvc; + @Autowired + EmpiLinkHelper myEmpiLinkHelper; + @Autowired + IEmpiStorageInterceptor myEmpiStorageInterceptor; + @Autowired + IInterceptorService myInterceptorService; + + private Person myDeletePerson; + private Person myKeepPerson; + private IdType myDeletePersonId; + private IdType myKeepPersonId; + private Long myDeletePersonPid; + private Long myKeepPersonPid; + private Patient myTargetPatient1; + private Patient myTargetPatient2; + private Patient myTargetPatient3; + + @Before + public void before() { + myDeletePerson = createPerson(); + myDeletePersonId = myDeletePerson.getIdElement().toUnqualifiedVersionless(); + myDeletePersonPid = myIdHelperService.getPidOrThrowException(myDeletePersonId); + myKeepPerson = createPerson(); + myKeepPersonId = myKeepPerson.getIdElement().toUnqualifiedVersionless(); + myKeepPersonPid = myIdHelperService.getPidOrThrowException(myKeepPersonId); + + myTargetPatient1 = createPatient(); + + myTargetPatient2 = createPatient(); + + myTargetPatient3 = createPatient(); + + // Register the empi storage interceptor after the creates so the delete hook is fired when we merge + myInterceptorService.registerInterceptor(myEmpiStorageInterceptor); + } + + @After + public void after() { + myInterceptorService.unregisterInterceptor(myEmpiStorageInterceptor); + super.after(); + } + + @Test + public void emptyMerge() { + assertEquals(2, getAllPersons().size()); + + Person mergedPerson = mergePersons(); + assertEquals(myKeepPerson.getIdElement(), mergedPerson.getIdElement()); + assertThat(mergedPerson, is(samePersonAs(mergedPerson))); + assertEquals(1, getAllPersons().size()); + } + + private Person mergePersons() { + return (Person) myEmpiPersonMergerSvc.mergePersons(myDeletePerson, myKeepPerson, createEmpiContext()); + } + + private EmpiTransactionContext createEmpiContext() { + return new EmpiTransactionContext(TransactionLogMessages.createFromTransactionGuid(UUID.randomUUID().toString()), EmpiTransactionContext.OperationType.MERGE_PERSONS); + } + + @Test + public void mergeRemovesPossibleDuplicatesLink() { + EmpiLink empiLink = new EmpiLink().setPersonPid(myKeepPersonPid).setTargetPid(myDeletePersonPid).setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE).setLinkSource(EmpiLinkSourceEnum.AUTO); + myEmpiLinkDaoSvc.save(empiLink); + assertEquals(1, myEmpiLinkDao.count()); + mergePersons(); + assertEquals(0, myEmpiLinkDao.count()); + } + + @Test + public void fullDeleteEmptyKeep() { + populatePerson(myDeletePerson); + + Person mergedPerson = mergePersons(); + HumanName returnedName = mergedPerson.getNameFirstRep(); + assertEquals(GIVEN_NAME, returnedName.getGivenAsSingleString()); + assertEquals(FAMILY_NAME, returnedName.getFamily()); + assertEquals(POSTAL_CODE, mergedPerson.getAddressFirstRep().getPostalCode()); + } + + @Test + public void emptyDeleteFullKeep() { + myDeletePerson.getName().add(new HumanName().addGiven(BAD_GIVEN_NAME)); + populatePerson(myKeepPerson); + + Person mergedPerson = mergePersons(); + HumanName returnedName = mergedPerson.getNameFirstRep(); + assertEquals(GIVEN_NAME, returnedName.getGivenAsSingleString()); + assertEquals(FAMILY_NAME, returnedName.getFamily()); + assertEquals(POSTAL_CODE, mergedPerson.getAddressFirstRep().getPostalCode()); + } + + @Test + public void deleteLinkKeepNoLink() { + createEmpiLink(myDeletePerson, myTargetPatient1); + + mergePersons(); + List links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myKeepPerson); + assertEquals(1, links.size()); + assertThat(myKeepPerson, is(possibleLinkedTo(myTargetPatient1))); + assertEquals(1, myKeepPerson.getLink().size()); + } + + @Test + public void deleteNoLinkKeepLink() { + createEmpiLink(myKeepPerson, myTargetPatient1); + + mergePersons(); + List links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myKeepPerson); + assertEquals(1, links.size()); + assertThat(myKeepPerson, is(possibleLinkedTo(myTargetPatient1))); + assertEquals(1, myKeepPerson.getLink().size()); + } + + @Test + public void deleteManualLinkOverridesAutoKeepLink() { + EmpiLink deleteLink = createEmpiLink(myDeletePerson, myTargetPatient1); + deleteLink.setLinkSource(EmpiLinkSourceEnum.MANUAL); + deleteLink.setMatchResult(EmpiMatchResultEnum.MATCH); + myEmpiLinkDaoSvc.save(deleteLink); + + createEmpiLink(myKeepPerson, myTargetPatient1); + + mergePersons(); + List links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myKeepPerson); + assertEquals(1, links.size()); + assertEquals(EmpiLinkSourceEnum.MANUAL, links.get(0).getLinkSource()); + } + + @Test + public void deleteManualNoMatchLinkOverridesAutoKeepLink() { + EmpiLink deleteLink = createEmpiLink(myDeletePerson, myTargetPatient1); + deleteLink.setLinkSource(EmpiLinkSourceEnum.MANUAL); + deleteLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH); + myEmpiLinkDaoSvc.save(deleteLink); + + createEmpiLink(myKeepPerson, myTargetPatient1); + + mergePersons(); + List links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myKeepPerson); + assertEquals(1, links.size()); + assertEquals(EmpiLinkSourceEnum.MANUAL, links.get(0).getLinkSource()); + } + + @Test + public void deleteManualAutoMatchLinkNoOverridesManualKeepLink() { + createEmpiLink(myDeletePerson, myTargetPatient1); + + EmpiLink keepLink = createEmpiLink(myKeepPerson, myTargetPatient1); + keepLink.setLinkSource(EmpiLinkSourceEnum.MANUAL); + keepLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH); + myEmpiLinkDaoSvc.save(keepLink); + + mergePersons(); + List links = myEmpiLinkDaoSvc.findEmpiLinksByPersonId(myKeepPerson); + assertEquals(1, links.size()); + assertEquals(EmpiLinkSourceEnum.MANUAL, links.get(0).getLinkSource()); + } + + @Test + public void deleteNoMatchMergeToManualMatchIsError() { + EmpiLink deleteLink = createEmpiLink(myDeletePerson, myTargetPatient1); + deleteLink.setLinkSource(EmpiLinkSourceEnum.MANUAL); + deleteLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH); + myEmpiLinkDaoSvc.save(deleteLink); + + EmpiLink keepLink = createEmpiLink(myKeepPerson, myTargetPatient1); + keepLink.setLinkSource(EmpiLinkSourceEnum.MANUAL); + keepLink.setMatchResult(EmpiMatchResultEnum.MATCH); + myEmpiLinkDaoSvc.save(keepLink); + + try { + mergePersons(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("A MANUAL NO_MATCH link may not be merged into a MANUAL MATCH link for the same target", e.getMessage()); + } + } + + @Test + public void deleteMatchMergeToManualNoMatchIsError() { + EmpiLink deleteLink = createEmpiLink(myDeletePerson, myTargetPatient1); + deleteLink.setLinkSource(EmpiLinkSourceEnum.MANUAL); + deleteLink.setMatchResult(EmpiMatchResultEnum.MATCH); + myEmpiLinkDaoSvc.save(deleteLink); + + EmpiLink keepLink = createEmpiLink(myKeepPerson, myTargetPatient1); + keepLink.setLinkSource(EmpiLinkSourceEnum.MANUAL); + keepLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH); + myEmpiLinkDaoSvc.save(keepLink); + + try { + mergePersons(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("A MANUAL MATCH link may not be merged into a MANUAL NO_MATCH link for the same target", e.getMessage()); + } + } + + @Test + public void deleteNoMatchMergeToManualMatchDifferentPatientIsOk() { + EmpiLink deleteLink = createEmpiLink(myDeletePerson, myTargetPatient1); + deleteLink.setLinkSource(EmpiLinkSourceEnum.MANUAL); + deleteLink.setMatchResult(EmpiMatchResultEnum.NO_MATCH); + myEmpiLinkDaoSvc.save(deleteLink); + + EmpiLink keepLink = createEmpiLink(myKeepPerson, myTargetPatient2); + keepLink.setLinkSource(EmpiLinkSourceEnum.MANUAL); + keepLink.setMatchResult(EmpiMatchResultEnum.MATCH); + myEmpiLinkDaoSvc.save(keepLink); + + mergePersons(); + assertEquals(1, myKeepPerson.getLink().size()); + assertEquals(2, myEmpiLinkDao.count()); + } + + @Test + public void delete123Keep1() { + createEmpiLink(myDeletePerson, myTargetPatient1); + createEmpiLink(myDeletePerson, myTargetPatient2); + createEmpiLink(myDeletePerson, myTargetPatient3); + createEmpiLink(myKeepPerson, myTargetPatient1); + + mergePersons(); + myEmpiLinkHelper.logEmpiLinks(); + + assertThat(myKeepPerson, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3))); + assertEquals(3, myKeepPerson.getLink().size()); + } + + @Test + public void delete1Keep123() { + createEmpiLink(myDeletePerson, myTargetPatient1); + createEmpiLink(myKeepPerson, myTargetPatient1); + createEmpiLink(myKeepPerson, myTargetPatient2); + createEmpiLink(myKeepPerson, myTargetPatient3); + + mergePersons(); + myEmpiLinkHelper.logEmpiLinks(); + + assertThat(myKeepPerson, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3))); + assertEquals(3, myKeepPerson.getLink().size()); + } + + @Test + public void delete123Keep123() { + createEmpiLink(myDeletePerson, myTargetPatient1); + createEmpiLink(myDeletePerson, myTargetPatient2); + createEmpiLink(myDeletePerson, myTargetPatient3); + createEmpiLink(myKeepPerson, myTargetPatient1); + createEmpiLink(myKeepPerson, myTargetPatient2); + createEmpiLink(myKeepPerson, myTargetPatient3); + + mergePersons(); + myEmpiLinkHelper.logEmpiLinks(); + + assertThat(myKeepPerson, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3))); + assertEquals(3, myKeepPerson.getLink().size()); + } + + @Test + public void delete12Keep23() { + createEmpiLink(myDeletePerson, myTargetPatient1); + createEmpiLink(myDeletePerson, myTargetPatient2); + createEmpiLink(myKeepPerson, myTargetPatient2); + createEmpiLink(myKeepPerson, myTargetPatient3); + + mergePersons(); + myEmpiLinkHelper.logEmpiLinks(); + + assertThat(myKeepPerson, is(possibleLinkedTo(myTargetPatient1, myTargetPatient2, myTargetPatient3))); + assertEquals(3, myKeepPerson.getLink().size()); + } + + private EmpiLink createEmpiLink(Person thePerson, Patient theTargetPatient) { + thePerson.addLink().setTarget(new Reference(theTargetPatient)); + return myEmpiLinkDaoSvc.createOrUpdateLinkEntity(thePerson, theTargetPatient, EmpiMatchResultEnum.POSSIBLE_MATCH, EmpiLinkSourceEnum.AUTO, createContextForCreate()); + } + + private void populatePerson(Person thePerson) { + thePerson.addName(new HumanName().addGiven(GIVEN_NAME).setFamily(FAMILY_NAME)); + thePerson.setGender(Enumerations.AdministrativeGender.FEMALE); + thePerson.setBirthDateElement(new DateType("1981-01-01")); + Address address = new Address(); + address.addLine("622 College St"); + address.addLine("Suite 401"); + address.setDistrict("Little Italy"); + address.setCity("Toronto"); + address.setCountry("Canada"); + address.setPostalCode(POSTAL_CODE); + thePerson.setAddress(Collections.singletonList(address)); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/test/resources/empi/empi-rules.json b/hapi-fhir-jpaserver-empi/src/test/resources/empi/empi-rules.json new file mode 100644 index 00000000000..78e77b6b054 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/resources/empi/empi-rules.json @@ -0,0 +1,35 @@ +{ + "candidateSearchParams" : [ { + "resourceType" : "Patient", + "searchParam" : "birthdate" + }, { + "resourceType" : "*", + "searchParam" : "identifier" + },{ + "resourceType" : "Patient", + "searchParam" : "general-practitioner" + } ], + "candidateFilterSearchParams" : [ { + "resourceType" : "*", + "searchParam" : "active", + "fixedValue" : "true" + } ], + "matchFields" : [ { + "name" : "given-name", + "resourceType" : "*", + "resourcePath" : "name.given", + "metric" : "COSINE", + "matchThreshold" : 0.8 + }, { + "name" : "last-name", + "resourceType" : "*", + "resourcePath" : "name.family", + "metric" : "JARO_WINKLER", + "matchThreshold" : 0.8 + }], + "matchResultMap" : { + "given-name" : "POSSIBLE_MATCH", + "given-name,last-name" : "MATCH" + }, + "eidSystem": "http://company.io/fhir/NamingSystem/custom-eid-system" +} diff --git a/hapi-fhir-jpaserver-empi/src/test/resources/logback-test.xml b/hapi-fhir-jpaserver-empi/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..5f5bbc5a80f --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/test/resources/logback-test.xml @@ -0,0 +1,74 @@ + + + + TRACE + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DEBUG + ${smile.basedir}/log/empi-troubleshooting.log + + ${smile.basedir}/log/empi-troubleshooting.log.%i.gz + 1 + 9 + + + 5MB + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n${log.stackfilter.pattern} + + + + + + + + + + + + diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/FlywayMigrator.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/FlywayMigrator.java index 4b24a3a3c63..c71c5d9b43d 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/FlywayMigrator.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/FlywayMigrator.java @@ -65,8 +65,6 @@ public class FlywayMigrator extends BaseMigrator { if (isDryRun()) { StringBuilder statementBuilder = buildExecutedStatementsString(); ourLog.info("SQL that would be executed:\n\n***********************************\n{}***********************************", statementBuilder); - } else { - ourLog.info("Schema migrated successfully."); } } catch (Exception e) { throw e; diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/SchemaMigrator.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/SchemaMigrator.java index f4b98c3f615..cf4262dbdf0 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/SchemaMigrator.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/SchemaMigrator.java @@ -41,6 +41,7 @@ import java.util.Properties; public class SchemaMigrator { public static final String HAPI_FHIR_MIGRATION_TABLENAME = "FLY_HFJ_MIGRATION"; private static final Logger ourLog = LoggerFactory.getLogger(SchemaMigrator.class); + private final String mySchemaName; private final DataSource myDataSource; private final boolean mySkipValidation; private final String myMigrationTableName; @@ -53,7 +54,8 @@ public class SchemaMigrator { /** * Constructor */ - public SchemaMigrator(String theMigrationTableName, DataSource theDataSource, Properties jpaProperties, List theMigrationTasks) { + public SchemaMigrator(String theSchemaName, String theMigrationTableName, DataSource theDataSource, Properties jpaProperties, List theMigrationTasks) { + mySchemaName = theSchemaName; myDataSource = theDataSource; myMigrationTableName = theMigrationTableName; myMigrationTasks = theMigrationTasks; @@ -105,7 +107,14 @@ public class SchemaMigrator { ourLog.warn("Database running in hibernate auto-update mode. Skipping schema migration."); return; } - newMigrator().migrate(); + try { + ourLog.info("Migrating " + mySchemaName); + newMigrator().migrate(); + ourLog.info(mySchemaName + " migrated successfully."); + } catch (Exception e) { + ourLog.error("Failed to migrate " + mySchemaName, e); + throw e; + } } private BaseMigrator newMigrator() { diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/TaskOnlyMigrator.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/TaskOnlyMigrator.java index 86d1d92bcd6..9b5450b9597 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/TaskOnlyMigrator.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/TaskOnlyMigrator.java @@ -65,10 +65,7 @@ public class TaskOnlyMigrator extends BaseMigrator { if (isDryRun()) { StringBuilder statementBuilder = buildExecutedStatementsString(); ourLog.info("SQL that would be executed:\n\n***********************************\n{}***********************************", statementBuilder); - } else { - ourLog.info("Schema migrated successfully."); } - } @Override diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index de34dfe61ef..4fda491d2aa 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -29,7 +29,14 @@ import ca.uhn.fhir.jpa.migrate.taskdef.CalculateOrdinalDatesTask; import ca.uhn.fhir.jpa.migrate.tasks.api.BaseMigrationTasks; import ca.uhn.fhir.jpa.migrate.tasks.api.Builder; import ca.uhn.fhir.jpa.model.config.PartitionSettings; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; +import ca.uhn.fhir.jpa.model.entity.SearchParamPresent; import ca.uhn.fhir.util.VersionEnum; import java.util.Arrays; @@ -64,67 +71,6 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { init501(); // 20200514 - present } - - /** - * Partway through the 4.3.0 releaase cycle we renumbered to - * 5.0.0 - We have a bunch of NOP tasks here to avoid breakage for anyone - * who installed a prerelease before we made the switch - */ - @SuppressWarnings("deprecation") - private void init430() { - Builder version = forVersion(VersionEnum.V4_3_0); - version.addNop("20200218.1"); - version.addNop("20200218.2"); - version.addNop("20200218.3"); - version.addNop("20200220.1"); - version.addNop("20200419.1"); - version.addNop("20200419.2"); - version.addNop("20200420.0"); - version.addNop("20200420.1"); - version.addNop("20200420.2"); - version.addNop("20200420.3"); - version.addNop("20200420.4"); - version.addNop("20200420.5"); - version.addNop("20200420.6"); - version.addNop("20200420.7"); - version.addNop("20200420.8"); - version.addNop("20200420.9"); - version.addNop("20200420.10"); - version.addNop("20200420.11"); - version.addNop("20200420.12"); - version.addNop("20200420.13"); - version.addNop("20200420.14"); - version.addNop("20200420.15"); - version.addNop("20200420.16"); - version.addNop("20200420.17"); - version.addNop("20200420.18"); - version.addNop("20200420.19"); - version.addNop("20200420.20"); - version.addNop("20200420.21"); - version.addNop("20200420.22"); - version.addNop("20200420.23"); - version.addNop("20200420.24"); - version.addNop("20200420.25"); - version.addNop("20200420.26"); - version.addNop("20200420.27"); - version.addNop("20200420.28"); - version.addNop("20200420.29"); - version.addNop("20200420.30"); - version.addNop("20200420.31"); - version.addNop("20200420.32"); - version.addNop("20200420.33"); - version.addNop("20200420.34"); - version.addNop("20200420.35"); - version.addNop("20200420.36"); - version.addNop("20200420.37"); - version.addNop("20200420.38"); - version.addNop("20200420.39"); - version.addNop("20200420.40"); - version.addNop("20200420.41"); - version.addNop("20200420.42"); - } - - private void init501() { //20200514 - present Builder version = forVersion(VersionEnum.V5_0_1); @@ -132,6 +78,31 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { spidxDate.addIndex("20200514.1", "IDX_SP_DATE_HASH_LOW").unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_LOW"); spidxDate.addIndex("20200514.2", "IDX_SP_DATE_ORD_HASH").unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_LOW_DATE_ORDINAL", "SP_VALUE_HIGH_DATE_ORDINAL"); spidxDate.addIndex("20200514.3", "IDX_SP_DATE_ORD_HASH_LOW").unique(false).withColumns("HASH_IDENTITY", "SP_VALUE_LOW_DATE_ORDINAL"); + + // MPI_LINK + version.addIdGenerator("20200517.1", "SEQ_EMPI_LINK_ID"); + Builder.BuilderAddTableByColumns empiLink = version.addTableByColumns("20200517.2", "MPI_LINK", "PID"); + empiLink.addColumn("PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + + empiLink.addColumn("PERSON_PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + empiLink + .addForeignKey("20200517.3", "FK_EMPI_LINK_PERSON") + .toColumn("PERSON_PID") + .references("HFJ_RESOURCE", "RES_ID"); + + empiLink.addColumn("TARGET_PID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.LONG); + empiLink + .addForeignKey("20200517.4", "FK_EMPI_LINK_TARGET") + .toColumn("TARGET_PID") + .references("HFJ_RESOURCE", "RES_ID"); + + empiLink.addColumn("MATCH_RESULT").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + empiLink.addColumn("LINK_SOURCE").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT); + empiLink.addColumn("CREATED").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP); + empiLink.addColumn("UPDATED").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.DATE_TIMESTAMP); + + + empiLink.addIndex("20200517.5", "IDX_EMPI_PERSON_TGT").unique(true).withColumns("PERSON_PID", "TARGET_PID"); } protected void init500() { // 20200218 - 20200519 @@ -209,7 +180,65 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { .addCalculator("SP_VALUE_HIGH_DATE_ORDINAL", t -> ResourceIndexedSearchParamDate.calculateOrdinalValue(t.getDate("SP_VALUE_HIGH"))) .setColumnName("SP_VALUE_LOW_DATE_ORDINAL") //It doesn't matter which of the two we choose as they will both be null. ); + } + /** + * Partway through the 4.3.0 releaase cycle we renumbered to + * 5.0.0 - We have a bunch of NOP tasks here to avoid breakage for anyone + * who installed a prerelease before we made the switch + */ + @SuppressWarnings("deprecation") + private void init430() { + Builder version = forVersion(VersionEnum.V4_3_0); + version.addNop("20200218.1"); + version.addNop("20200218.2"); + version.addNop("20200218.3"); + version.addNop("20200220.1"); + version.addNop("20200419.1"); + version.addNop("20200419.2"); + version.addNop("20200420.0"); + version.addNop("20200420.1"); + version.addNop("20200420.2"); + version.addNop("20200420.3"); + version.addNop("20200420.4"); + version.addNop("20200420.5"); + version.addNop("20200420.6"); + version.addNop("20200420.7"); + version.addNop("20200420.8"); + version.addNop("20200420.9"); + version.addNop("20200420.10"); + version.addNop("20200420.11"); + version.addNop("20200420.12"); + version.addNop("20200420.13"); + version.addNop("20200420.14"); + version.addNop("20200420.15"); + version.addNop("20200420.16"); + version.addNop("20200420.17"); + version.addNop("20200420.18"); + version.addNop("20200420.19"); + version.addNop("20200420.20"); + version.addNop("20200420.21"); + version.addNop("20200420.22"); + version.addNop("20200420.23"); + version.addNop("20200420.24"); + version.addNop("20200420.25"); + version.addNop("20200420.26"); + version.addNop("20200420.27"); + version.addNop("20200420.28"); + version.addNop("20200420.29"); + version.addNop("20200420.30"); + version.addNop("20200420.31"); + version.addNop("20200420.32"); + version.addNop("20200420.33"); + version.addNop("20200420.34"); + version.addNop("20200420.35"); + version.addNop("20200420.36"); + version.addNop("20200420.37"); + version.addNop("20200420.38"); + version.addNop("20200420.39"); + version.addNop("20200420.40"); + version.addNop("20200420.41"); + version.addNop("20200420.42"); } protected void init420() { // 20191015 - 20200217 diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/SchemaMigratorTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/SchemaMigratorTest.java index ee6867ba74b..79d296a6cf1 100644 --- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/SchemaMigratorTest.java +++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/SchemaMigratorTest.java @@ -18,7 +18,9 @@ import java.util.function.Supplier; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.Matchers.endsWith; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; public class SchemaMigratorTest extends BaseTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SchemaMigratorTest.class); @@ -95,7 +97,7 @@ public class SchemaMigratorTest extends BaseTest { ImmutableList taskList = ImmutableList.of(taskA, taskB, taskC, taskD); MigrationTaskSkipper.setDoNothingOnSkippedTasks(taskList, "4_1_0.20191214.2, 4_1_0.20191214.4"); - SchemaMigrator schemaMigrator = new SchemaMigrator(SchemaMigrator.HAPI_FHIR_MIGRATION_TABLENAME, getDataSource(), new Properties(), taskList); + SchemaMigrator schemaMigrator = new SchemaMigrator(getUrl(), SchemaMigrator.HAPI_FHIR_MIGRATION_TABLENAME, getDataSource(), new Properties(), taskList); schemaMigrator.setDriverType(getDriverType()); schemaMigrator.migrate(); @@ -134,7 +136,7 @@ public class SchemaMigratorTest extends BaseTest { AddTableRawSqlTask task = new AddTableRawSqlTask("1", theSchemaVersion); task.setTableName(theTableName); task.addSql(getDriverType(), theSql); - SchemaMigrator retval = new SchemaMigrator(SchemaMigrator.HAPI_FHIR_MIGRATION_TABLENAME, getDataSource(), new Properties(), ImmutableList.of(task)); + SchemaMigrator retval = new SchemaMigrator(getUrl(), SchemaMigrator.HAPI_FHIR_MIGRATION_TABLENAME, getDataSource(), new Properties(), ImmutableList.of(task)); retval.setDriverType(getDriverType()); return retval; } diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml index 77eed9b3664..0d091ad7b4d 100644 --- a/hapi-fhir-jpaserver-model/pom.xml +++ b/hapi-fhir-jpaserver-model/pom.xml @@ -88,6 +88,10 @@ org.springframework spring-beans + + org.springframework + spring-messaging + org.springframework spring-context diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java index 0f9849dd2f8..0e68f8e3903 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamString.java @@ -22,9 +22,9 @@ package ca.uhn.fhir.jpa.model.entity; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.model.config.PartitionSettings; -import ca.uhn.fhir.jpa.model.util.StringNormalizer; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.util.StringNormalizer; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; @@ -266,7 +266,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP return false; } StringParam string = (StringParam) theParam; - String normalizedString = StringNormalizer.normalizeString(defaultString(string.getValue())); + String normalizedString = StringNormalizer.normalizeStringForSearchIndexing(defaultString(string.getValue())); return defaultString(getValueNormalized()).startsWith(normalizedString); } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java index b70b7e25129..9629215e5fd 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamToken.java @@ -40,7 +40,6 @@ import javax.persistence.Id; import javax.persistence.Index; import javax.persistence.SequenceGenerator; import javax.persistence.Table; -import javax.validation.constraints.NotNull; import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.trim; @@ -54,9 +53,13 @@ import static org.apache.commons.lang3.StringUtils.trim; * IDX_SP_TOKEN * IDX_SP_TOKEN_UNQUAL */ + + // TODO PERF Recommend to drop this index: @Index(name = "IDX_SP_TOKEN_HASH", columnList = "HASH_IDENTITY"), @Index(name = "IDX_SP_TOKEN_HASH_S", columnList = "HASH_SYS"), @Index(name = "IDX_SP_TOKEN_HASH_SV", columnList = "HASH_SYS_AND_VALUE"), + // TODO PERF change this to: + // @Index(name = "IDX_SP_TOKEN_HASH_V", columnList = "HASH_VALUE,RES_ID"), @Index(name = "IDX_SP_TOKEN_HASH_V", columnList = "HASH_VALUE"), @Index(name = "IDX_SP_TOKEN_UPDATED", columnList = "SP_UPDATED"), diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/util/JpaInterceptorBroadcaster.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/util/JpaInterceptorBroadcaster.java index 7043deb7375..9a071c6fb5e 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/util/JpaInterceptorBroadcaster.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/util/JpaInterceptorBroadcaster.java @@ -25,13 +25,15 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.rest.api.server.RequestDetails; +import javax.annotation.Nullable; + public class JpaInterceptorBroadcaster { /** * Broadcast hooks to both the interceptor service associated with the request, as well * as the one associated with the JPA module. */ - public static boolean doCallHooks(IInterceptorBroadcaster theInterceptorBroadcaster, RequestDetails theRequestDetails, Pointcut thePointcut, HookParams theParams) { + public static boolean doCallHooks(IInterceptorBroadcaster theInterceptorBroadcaster, @Nullable RequestDetails theRequestDetails, Pointcut thePointcut, HookParams theParams) { boolean retVal = true; if (theInterceptorBroadcaster != null) { retVal = theInterceptorBroadcaster.callHooks(thePointcut, theParams); diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java index 41ce91a6e52..de12b8555f4 100644 --- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java +++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamDateTest.java @@ -5,7 +5,6 @@ import org.junit.Before; import org.junit.Test; import java.sql.Timestamp; -import java.time.Instant; import java.util.Calendar; import java.util.Date; diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceTableTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceTableTest.java index 28a5c1049e0..aaec502ecd1 100644 --- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceTableTest.java +++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceTableTest.java @@ -9,7 +9,7 @@ public class ResourceTableTest { @Test public void testResourceLength() { - for (String nextName : FhirContext.forR4().getResourceNames()) { + for (String nextName : FhirContext.forR4().getResourceTypes()) { if (nextName.length() > ResourceTable.RESTYPE_LEN) { fail("Name " + nextName + " length of " + nextName.length() + " is > " + ResourceTable.RESTYPE_LEN); } diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/util/StringNormalizerTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/util/StringNormalizerTest.java index 8995d2db71f..01a687fa89f 100644 --- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/util/StringNormalizerTest.java +++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/util/StringNormalizerTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.model.util; +import ca.uhn.fhir.util.StringNormalizer; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -7,8 +8,8 @@ import static org.junit.Assert.assertEquals; public class StringNormalizerTest { @Test public void testNormalizeString() { - assertEquals("TEST TEST", StringNormalizer.normalizeString("TEST teSt")); - assertEquals("AEIØU", StringNormalizer.normalizeString("åéîøü")); - assertEquals("杨浩", StringNormalizer.normalizeString("杨浩")); + assertEquals("TEST TEST", StringNormalizer.normalizeStringForSearchIndexing("TEST teSt")); + assertEquals("AEIØU", StringNormalizer.normalizeStringForSearchIndexing("åéîøü")); + assertEquals("杨浩", StringNormalizer.normalizeStringForSearchIndexing("杨浩")); } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java index 7b4e98ae21e..6ef228f30c1 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java @@ -37,7 +37,6 @@ import ca.uhn.fhir.util.UrlUtil; import com.google.common.collect.ArrayListMultimap; import org.apache.http.NameValuePair; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import java.util.List; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index 62a6b738a2f..9c42925f4cf 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -38,14 +38,14 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; import ca.uhn.fhir.jpa.model.util.JpaConstants; -import ca.uhn.fhir.jpa.model.util.StringNormalizer; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.model.primitive.BoundCodeDt; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import com.google.common.annotations.VisibleForTesting; +import ca.uhn.fhir.util.StringNormalizer; import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.hibernate.search.spatial.impl.Point; import org.hl7.fhir.exceptions.FHIRException; @@ -157,7 +157,12 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor @Override public SearchParamSet extractResourceLinks(IBaseResource theResource) { - IExtractor extractor = (params, searchParam, value, path) -> { + IExtractor extractor = createReferenceExtractor(); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.REFERENCE); + } + + private IExtractor createReferenceExtractor() { + return (params, searchParam, value, path) -> { if (value instanceof IBaseResource) { return; } @@ -221,10 +226,67 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor break; } }; - - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.REFERENCE); } + @Override + public List extractParamValuesAsStrings(RuntimeSearchParam theSearchParam, IBaseResource theResource) { + IExtractor extractor; + switch(theSearchParam.getParamType()) { + case DATE: + extractor = createDateExtractor(theResource); + break; + case STRING: + extractor = createStringExtractor(theResource); + break; + case TOKEN: + extractor = createTokenExtractor(theResource); + break; + case NUMBER: + extractor = createNumberExtractor(theResource); + break; + case REFERENCE: + extractor = createReferenceExtractor(); + return extractReferenceParamsAsQueryTokens(theSearchParam, theResource, extractor); + case QUANTITY: + extractor = createQuantityExtractor(theResource); + break; + case URI: + extractor = createUriExtractor(theResource); + break; + case SPECIAL: + extractor = createSpecialExtractor(theResource.getIdElement().getResourceType()); + break; + case COMPOSITE: + default: + throw new UnsupportedOperationException("Type " + theSearchParam.getParamType() + " not supported for extraction"); + } + return extractParamsAsQueryTokens(theSearchParam, theResource, extractor); + } + + private List extractReferenceParamsAsQueryTokens(RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor theExtractor) { + SearchParamSet params = new SearchParamSet<>(); + extractSearchParam(theSearchParam, theResource, theExtractor, params); + return refsToStringList(params); + } + + private List refsToStringList(SearchParamSet theParams) { + return theParams.stream() + .map(PathAndRef::getRef) + .map(ref -> ref.getReferenceElement().toUnqualifiedVersionless().getValue()) + .collect(Collectors.toList()); + } + + private List extractParamsAsQueryTokens(RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor theExtractor) { + SearchParamSet params = new SearchParamSet<>(); + extractSearchParam(theSearchParam, theResource, theExtractor, params); + return toStringList(params); + } + + private List toStringList(SearchParamSet theParams) { + return theParams.stream() + .map(param -> param.toQueryParameterType().getValueAsQueryToken(myContext)) + .collect(Collectors.toList()); + } @Override public SearchParamSet extractSearchParamTokens(IBaseResource theResource) { @@ -263,16 +325,17 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor @Override public SearchParamSet extractSearchParamSpecial(IBaseResource theResource) { - String resourceTypeName = toRootTypeName(theResource); + IExtractor extractor = createSpecialExtractor(resourceTypeName); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.SPECIAL); + } - IExtractor extractor = (params, searchParam, value, path) -> { + private IExtractor createSpecialExtractor(String theResourceTypeName) { + return (params, searchParam, value, path) -> { if ("Location.position".equals(path)) { - addCoords_Position(resourceTypeName, params, searchParam, value); + addCoords_Position(theResourceTypeName, params, searchParam, value); } }; - - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.SPECIAL); } @@ -282,7 +345,12 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor @Override public SearchParamSet extractSearchParamUri(IBaseResource theResource) { - IExtractor extractor = (params, searchParam, value, path) -> { + IExtractor extractor = createUriExtractor(theResource); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI); + } + + private IExtractor createUriExtractor(IBaseResource theResource) { + return (params, searchParam, value, path) -> { String nextType = toRootTypeName(value); String resourceType = toRootTypeName(theResource); switch (nextType) { @@ -298,13 +366,16 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor break; } }; - - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI); } @Override public SearchParamSet extractSearchParamDates(IBaseResource theResource) { - IExtractor extractor = (params, searchParam, value, path) -> { + IExtractor extractor = createDateExtractor(theResource); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.DATE); + } + + private IExtractor createDateExtractor(IBaseResource theResource) { + return (params, searchParam, value, path) -> { String nextType = toRootTypeName(value); String resourceType = toRootTypeName(theResource); switch (nextType) { @@ -327,14 +398,16 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor break; } }; - - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.DATE); } @Override public SearchParamSet extractSearchParamNumber(IBaseResource theResource) { + IExtractor extractor = createNumberExtractor(theResource); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER); + } - IExtractor extractor = (params, searchParam, value, path) -> { + private IExtractor createNumberExtractor(IBaseResource theResource) { + return (params, searchParam, value, path) -> { String nextType = toRootTypeName(value); String resourceType = toRootTypeName(theResource); switch (nextType) { @@ -357,14 +430,16 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor break; } }; - - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER); } @Override public SearchParamSet extractSearchParamQuantity(IBaseResource theResource) { + IExtractor extractor = createQuantityExtractor(theResource); + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY); + } - IExtractor extractor = (params, searchParam, value, path) -> { + private IExtractor createQuantityExtractor(IBaseResource theResource) { + return (params, searchParam, value, path) -> { if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) { return; } @@ -386,13 +461,17 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor break; } }; - - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY); } @Override public SearchParamSet extractSearchParamStrings(IBaseResource theResource) { - IExtractor extractor = (params, searchParam, value, path) -> { + IExtractor extractor = createStringExtractor(theResource); + + return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING); + } + + private IExtractor createStringExtractor(IBaseResource theResource) { + return (params, searchParam, value, path) -> { String resourceType = toRootTypeName(theResource); if (value instanceof IPrimitiveType) { @@ -424,8 +503,6 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor break; } }; - - return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING); } /** @@ -856,7 +933,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor } String searchParamName = theSearchParam.getName(); - String valueNormalized = StringNormalizer.normalizeString(value); + String valueNormalized = StringNormalizer.normalizeStringForSearchIndexing(value); if (valueNormalized.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { valueNormalized = valueNormalized.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); } @@ -1153,7 +1230,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor return theChildDefinition .getAccessor() .>getFirstValueOrNull(theElement) - .map(t -> t.getValueAsString()) + .map(IPrimitiveType::getValueAsString) .orElse(null); } @@ -1161,7 +1238,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor return theChildDefinition .getAccessor() .>getFirstValueOrNull(theElement) - .map(t -> t.getValue()) + .map(IPrimitiveType::getValue) .orElse(null); } @@ -1169,7 +1246,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor return theChildDefinition .getAccessor() .>getFirstValueOrNull(theElement) - .map(t -> t.getValue()) + .map(IPrimitiveType::getValue) .orElse(null); } @@ -1186,8 +1263,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor .getValues(theValue) .stream() .map(t -> (IPrimitiveType) t) - .map(t -> t.getValueAsString()) - .filter(t -> isNotBlank(t)) + .map(IPrimitiveType::getValueAsString) + .filter(StringUtils::isNotBlank) .collect(Collectors.toList()); } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java index 15e4479ab8c..790b17ba357 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java @@ -1,7 +1,12 @@ package ca.uhn.fhir.jpa.searchparam.extractor; import ca.uhn.fhir.context.RuntimeSearchParam; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; import org.hl7.fhir.instance.model.api.IBaseResource; import java.util.ArrayList; @@ -53,6 +58,8 @@ public interface ISearchParamExtractor { String[] split(String theExpression); + List extractParamValuesAsStrings(RuntimeSearchParam theActiveSearchParam, IBaseResource theResource); + class SearchParamSet extends HashSet { diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java index 513a5fcfe49..bae7ac93b14 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java @@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.searchparam.extractor; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.model.config.PartitionSettings; -import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java index de22e391048..abf9d099ba0 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java @@ -29,7 +29,6 @@ import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.cross.IResourceLookup; -import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords; @@ -42,12 +41,13 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; -import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; +import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import com.google.common.annotations.VisibleForTesting; @@ -61,6 +61,7 @@ import javax.annotation.Nonnull; import javax.validation.constraints.NotNull; import java.util.Collection; import java.util.Date; +import java.util.List; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -70,6 +71,7 @@ public class SearchParamExtractorService { @Autowired private ISearchParamExtractor mySearchParamExtractor; + @Autowired private IInterceptorBroadcaster myInterceptorBroadcaster; @Autowired @@ -172,7 +174,7 @@ public class SearchParamExtractorService { } private void extractResourceLinks(RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference, RequestDetails theRequest) { - String resourceName = myContext.getResourceDefinition(theResource).getName(); + String resourceName = myContext.getResourceType(theResource); ISearchParamExtractor.SearchParamSet refs = mySearchParamExtractor.extractResourceLinks(theResource); SearchParamExtractorService.handleWarnings(theRequest, myInterceptorBroadcaster, refs); @@ -395,5 +397,9 @@ public class SearchParamExtractorService { void setInterceptorBroadcasterForUnitTest(IInterceptorBroadcaster theJpaInterceptorBroadcaster) { myInterceptorBroadcaster = theJpaInterceptorBroadcaster; } + + public List extractParamValuesAsStrings(RuntimeSearchParam theActiveSearchParam, IBaseResource theResource) { + return mySearchParamExtractor.extractParamValuesAsStrings(theActiveSearchParam, theResource); + } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/IndexedSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/IndexedSearchParamExtractor.java index d26fc02de9d..f1caa625c9b 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/IndexedSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/matcher/IndexedSearchParamExtractor.java @@ -22,10 +22,10 @@ package ca.uhn.fhir.jpa.searchparam.matcher; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; @@ -38,7 +38,7 @@ public class IndexedSearchParamExtractor { public ResourceIndexedSearchParams extractIndexedSearchParams(IBaseResource theResource, RequestDetails theRequest) { ResourceTable entity = new ResourceTable(); TransactionDetails transactionDetails = new TransactionDetails(); - String resourceType = myContext.getResourceDefinition(theResource).getName(); + String resourceType = myContext.getResourceType(theResource); entity.setResourceType(resourceType); ResourceIndexedSearchParams resourceIndexedSearchParams = new ResourceIndexedSearchParams(); mySearchParamExtractorService.extractFromResource(null, theRequest, resourceIndexedSearchParams, entity, theResource, transactionDetails, false); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java index 8bb35c3306e..5cc250bb731 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java @@ -783,7 +783,7 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry { public static Map> createBuiltInSearchParamMap(FhirContext theFhirContext) { Map> resourceNameToSearchParams = new HashMap<>(); - Set resourceNames = theFhirContext.getResourceNames(); + Set resourceNames = theFhirContext.getResourceTypes(); for (String resourceName : resourceNames) { RuntimeResourceDefinition nextResDef = theFhirContext.getResourceDefinition(resourceName); diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java index 92e4c8e642c..e910b0a4403 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java @@ -15,11 +15,11 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; -import ca.uhn.fhir.jpa.model.util.StringNormalizer; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; +import ca.uhn.fhir.util.StringNormalizer; import ca.uhn.fhir.util.TestUtil; import com.google.common.collect.Sets; import org.hamcrest.Matchers; @@ -74,7 +74,7 @@ public class SearchParamExtractorDstu3Test { String value = IntStream.range(1, 200).mapToObj(v -> "a").collect(Collectors.joining()) + "ئ"; assertEquals(value.length(), 200); assertEquals(Normalizer.normalize(value, Normalizer.Form.NFD).length(), 201); - assertEquals(StringNormalizer.normalizeString(value).length(), 201); + assertEquals(StringNormalizer.normalizeStringForSearchIndexing(value).length(), 201); Questionnaire questionnaire = new Questionnaire(); questionnaire.setDescription(value); diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java index 58124e39de6..7d9ae2e0fdf 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java @@ -74,7 +74,7 @@ public class SearchParamExtractorMegaTest { private void process(FhirContext theCtx, BaseSearchParamExtractor theExtractor) throws Exception { AtomicInteger indexesCounter = new AtomicInteger(); - for (String nextResourceName : theCtx.getResourceNames()) { + for (String nextResourceName : theCtx.getResourceTypes()) { RuntimeResourceDefinition resourceDefinition = theCtx.getResourceDefinition(nextResourceName); List elementStack = new ArrayList<>(); diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamFinder.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamFinder.java index 4b48c80f617..977a2a29a44 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamFinder.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamFinder.java @@ -28,7 +28,7 @@ public class SearchParamFinder { } public static void process(FhirContext theCtx, RestSearchParameterTypeEnum theWantType) { - for (String nextResourceName : theCtx.getResourceNames()) { + for (String nextResourceName : theCtx.getResourceTypes()) { RuntimeResourceDefinition nextResDef = theCtx.getResourceDefinition(nextResourceName); for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) { if (nextSp.getName().equals("_id")){ diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/BaseChannelSettings.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/BaseChannelSettings.java new file mode 100644 index 00000000000..417f5e3b83e --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/BaseChannelSettings.java @@ -0,0 +1,20 @@ +package ca.uhn.fhir.jpa.subscription.channel.api; + +public abstract class BaseChannelSettings implements IChannelSettings { + private boolean myQualifyChannelName = true; + + /** + * Default true. Used by IChannelNamer to decide how to qualify the channel name. + */ + @Override + public boolean isQualifyChannelName() { + return myQualifyChannelName; + } + + /** + * Default true. Used by IChannelNamer to decide how to qualify the channel name. + */ + public void setQualifyChannelName(boolean theQualifyChannelName) { + myQualifyChannelName = theQualifyChannelName; + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/ChannelConsumerSettings.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/ChannelConsumerSettings.java index 5d17afd3767..1b8aa3735c3 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/ChannelConsumerSettings.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/ChannelConsumerSettings.java @@ -20,7 +20,10 @@ package ca.uhn.fhir.jpa.subscription.channel.api; * #L% */ -public class ChannelConsumerSettings { +public class ChannelConsumerSettings extends BaseChannelSettings { + public static final Integer DEFAULT_CHANNEL_CONSUMERS = 2; + + private Integer myConcurrentConsumers = DEFAULT_CHANNEL_CONSUMERS; /** * Constructor @@ -29,8 +32,6 @@ public class ChannelConsumerSettings { super(); } - private Integer myConcurrentConsumers; - public Integer getConcurrentConsumers() { return myConcurrentConsumers; } @@ -39,5 +40,4 @@ public class ChannelConsumerSettings { myConcurrentConsumers = theConcurrentConsumers; return this; } - } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/ChannelProducerSettings.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/ChannelProducerSettings.java new file mode 100644 index 00000000000..8d2a9cce0f7 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/ChannelProducerSettings.java @@ -0,0 +1,43 @@ +package ca.uhn.fhir.jpa.subscription.channel.api; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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 class ChannelProducerSettings extends BaseChannelSettings { + public static final Integer DEFAULT_CHANNEL_CONSUMERS = 2; + + private Integer myConcurrentConsumers = DEFAULT_CHANNEL_CONSUMERS; + + /** + * Constructor + */ + public ChannelProducerSettings() { + super(); + } + + public Integer getConcurrentConsumers() { + return myConcurrentConsumers; + } + + public ChannelProducerSettings setConcurrentConsumers(int theConcurrentConsumers) { + myConcurrentConsumers = theConcurrentConsumers; + return this; + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelFactory.java index 0da00876de6..7418cf6c703 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelFactory.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelFactory.java @@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.subscription.channel.api; * #L% */ +import ca.uhn.fhir.jpa.subscription.channel.subscription.IChannelNamer; + /** * This interface is the factory for Queue Channels, which are the low level abstraction over a * queue (e.g. memory queue, JMS queue, Kafka stream, etc.) for any purpose. @@ -36,14 +38,9 @@ public interface IChannelFactory { * * @param theChannelName The actual underlying queue name * @param theMessageType The object type that will be placed on this queue. Objects will be Jackson-annotated structures. - * @param theConfig Contains the configuration for subscribers. Note that this parameter is provided for - * both {@link #getOrCreateReceiver} and - * {@link #getOrCreateProducer(String, Class, ChannelConsumerSettings)} - * even though this object is used to configure the sender only. We do this because the factory - * may want to create a single object to be used for both the sender and receiver, so this allows - * the config details to be known regardless of which method is returned first. + * @param theChannelSettings Contains the configuration for subscribers. */ - IChannelReceiver getOrCreateReceiver(String theChannelName, Class theMessageType, ChannelConsumerSettings theConfig); + IChannelReceiver getOrCreateReceiver(String theChannelName, Class theMessageType, ChannelConsumerSettings theChannelSettings); /** * Create a channel that is used to send messages to the queue. @@ -55,13 +52,12 @@ public interface IChannelFactory { * * @param theChannelName The actual underlying queue name * @param theMessageType The object type that will be placed on this queue. Objects will be Jackson-annotated structures. - * @param theConfig Contains the configuration for subscribers. Note that this parameter is provided for - * both {@link #getOrCreateReceiver} and - * {@link #getOrCreateProducer(String, Class, ChannelConsumerSettings)} - * even though this object is used to configure the sender only. We do this because the factory - * may want to create a single object to be used for both the sender and receiver, so this allows - * the config details to be known regardless of which method is returned first. + * @param theChannelSettings Contains the configuration for senders. */ - IChannelProducer getOrCreateProducer(String theChannelName, Class theMessageType, ChannelConsumerSettings theConfig); + IChannelProducer getOrCreateProducer(String theChannelName, Class theMessageType, ChannelProducerSettings theChannelSettings); + /** + * @return the IChannelNamer used by this factory + */ + IChannelNamer getChannelNamer(); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelReceiver.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelReceiver.java index 4c3677c4760..4fd2a193e7f 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelReceiver.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelReceiver.java @@ -24,4 +24,5 @@ import org.springframework.messaging.SubscribableChannel; import org.springframework.messaging.support.InterceptableChannel; public interface IChannelReceiver extends SubscribableChannel, InterceptableChannel { + String getName(); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelSettings.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelSettings.java new file mode 100644 index 00000000000..a1563b38263 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelSettings.java @@ -0,0 +1,5 @@ +package ca.uhn.fhir.jpa.subscription.channel.api; + +public interface IChannelSettings { + boolean isQualifyChannelName(); +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/config/SubscriptionChannelConfig.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/config/SubscriptionChannelConfig.java index a56bd4f4968..3a1834c58aa 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/config/SubscriptionChannelConfig.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/config/SubscriptionChannelConfig.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.subscription.channel.config; import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory; import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.subscription.IChannelNamer; import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -33,8 +34,8 @@ public class SubscriptionChannelConfig { * Create a @Primary @Bean if you need a different implementation */ @Bean - public IChannelFactory queueChannelFactory() { - return new LinkedBlockingChannelFactory(); + public IChannelFactory queueChannelFactory(IChannelNamer theChannelNamer) { + return new LinkedBlockingChannelFactory(theChannelNamer); } @Bean @@ -42,4 +43,12 @@ public class SubscriptionChannelConfig { return new SubscriptionChannelFactory(theQueueChannelFactory); } + /** + * Create a @Primary @Bean if you need a different implementation + */ + @Bean + // Default implementation returns the name unchanged + public IChannelNamer channelNamer() { + return (theNameComponent, theChannelSettings) -> theNameComponent; + } } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannel.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannel.java index 5c5565de963..c4a647493c7 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannel.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannel.java @@ -29,10 +29,12 @@ import java.util.concurrent.ThreadPoolExecutor; public class LinkedBlockingChannel extends ExecutorSubscribableChannel implements IChannelProducer, IChannelReceiver { + private final String myName; private final BlockingQueue myQueue; - public LinkedBlockingChannel(ThreadPoolExecutor theExecutor, BlockingQueue theQueue) { + public LinkedBlockingChannel(String theName, ThreadPoolExecutor theExecutor, BlockingQueue theQueue) { super(theExecutor); + myName = theName; myQueue = theQueue; } @@ -45,4 +47,9 @@ public class LinkedBlockingChannel extends ExecutorSubscribableChannel implement removeInterceptor(0); } } + + @Override + public String getName() { + return myName; + } } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannelFactory.java index 64840beace8..17d8bbf2848 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannelFactory.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/impl/LinkedBlockingChannelFactory.java @@ -21,9 +21,12 @@ package ca.uhn.fhir.jpa.subscription.channel.impl; */ import ca.uhn.fhir.jpa.subscription.channel.api.ChannelConsumerSettings; +import ca.uhn.fhir.jpa.subscription.channel.api.ChannelProducerSettings; import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory; import ca.uhn.fhir.jpa.subscription.channel.api.IChannelProducer; import ca.uhn.fhir.jpa.subscription.channel.api.IChannelReceiver; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelSettings; +import ca.uhn.fhir.jpa.subscription.channel.subscription.IChannelNamer; import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionConstants; import ca.uhn.fhir.util.StopWatch; import org.apache.commons.lang3.concurrent.BasicThreadFactory; @@ -45,28 +48,33 @@ public class LinkedBlockingChannelFactory implements IChannelFactory { private static final Logger ourLog = LoggerFactory.getLogger(LinkedBlockingChannelFactory.class); private Map myChannels = Collections.synchronizedMap(new HashMap<>()); + private final IChannelNamer myChannelNamer; - /** - * Constructor - */ - public LinkedBlockingChannelFactory() { - super(); + public LinkedBlockingChannelFactory(IChannelNamer theChannelNamer) { + myChannelNamer = theChannelNamer; } @Override - public IChannelReceiver getOrCreateReceiver(String theChannelName, Class theMessageType, ChannelConsumerSettings theConfig) { - return getOrCreateChannel(theChannelName, theConfig.getConcurrentConsumers()); + public IChannelReceiver getOrCreateReceiver(String theChannelName, Class theMessageType, ChannelConsumerSettings theChannelSettings) { + return getOrCreateChannel(theChannelName, theChannelSettings.getConcurrentConsumers(), theChannelSettings); } @Override - public IChannelProducer getOrCreateProducer(String theChannelName, Class theMessageType, ChannelConsumerSettings theConfig) { - return getOrCreateChannel(theChannelName, theConfig.getConcurrentConsumers()); + public IChannelProducer getOrCreateProducer(String theChannelName, Class theMessageType, ChannelProducerSettings theChannelSettings) { + return getOrCreateChannel(theChannelName, theChannelSettings.getConcurrentConsumers(), theChannelSettings); } - private LinkedBlockingChannel getOrCreateChannel(String theChannelName, int theConcurrentConsumers) { - return myChannels.computeIfAbsent(theChannelName, t -> { + @Override + public IChannelNamer getChannelNamer() { + return myChannelNamer; + } - String threadNamingPattern = theChannelName + "-%d"; + private LinkedBlockingChannel getOrCreateChannel(String theChannelName, int theConcurrentConsumers, IChannelSettings theChannelSettings) { + final String channelName = myChannelNamer.getChannelName(theChannelName, theChannelSettings); + + return myChannels.computeIfAbsent(channelName, t -> { + + String threadNamingPattern = channelName + "-%d"; ThreadFactory threadFactory = new BasicThreadFactory.Builder() .namingPattern(threadNamingPattern) @@ -95,7 +103,7 @@ public class LinkedBlockingChannelFactory implements IChannelFactory { queue, threadFactory, rejectedExecutionHandler); - return new LinkedBlockingChannel(executor, queue); + return new LinkedBlockingChannel(channelName, executor, queue); }); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/IChannelNamer.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/IChannelNamer.java new file mode 100644 index 00000000000..596ee36ef06 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/IChannelNamer.java @@ -0,0 +1,34 @@ +package ca.uhn.fhir.jpa.subscription.channel.subscription; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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.channel.api.IChannelSettings; + +public interface IChannelNamer { + /** + * Channel factories call this service to qualify the channel name before sending it to the channel factory. + * + * @param theNameComponent the component of the queue or topic name + * @param theChannelSettings + * @return the fully qualified the channel factory will use to name the queue or topic + */ + String getChannelName(String theNameComponent, IChannelSettings theChannelSettings); +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactory.java index 57494d9d569..2ad7287f8d4 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactory.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactory.java @@ -20,13 +20,14 @@ package ca.uhn.fhir.jpa.subscription.channel.subscription; * #L% */ -import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory; -import ca.uhn.fhir.jpa.subscription.channel.api.IChannelReceiver; -import ca.uhn.fhir.jpa.subscription.channel.api.IChannelProducer; import ca.uhn.fhir.jpa.subscription.channel.api.ChannelConsumerSettings; +import ca.uhn.fhir.jpa.subscription.channel.api.ChannelProducerSettings; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelProducer; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelReceiver; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionConstants; import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryJsonMessage; import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; -import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionConstants; import org.apache.commons.lang3.Validate; import org.springframework.beans.factory.DisposableBean; import org.springframework.messaging.Message; @@ -36,46 +37,57 @@ import org.springframework.messaging.support.AbstractSubscribableChannel; import org.springframework.messaging.support.ChannelInterceptor; public class SubscriptionChannelFactory { - - private final IChannelFactory myQueueChannelFactory; + private final IChannelFactory myChannelFactory; /** * Constructor */ - public SubscriptionChannelFactory(IChannelFactory theQueueChannelFactory) { - Validate.notNull(theQueueChannelFactory); - myQueueChannelFactory = theQueueChannelFactory; + public SubscriptionChannelFactory(IChannelFactory theChannelFactory) { + Validate.notNull(theChannelFactory); + myChannelFactory = theChannelFactory; } - public IChannelProducer newDeliverySendingChannel(String theChannelName, ChannelConsumerSettings theOptions) { - ChannelConsumerSettings config = newConfigForDeliveryChannel(theOptions); - return myQueueChannelFactory.getOrCreateProducer(theChannelName, ResourceDeliveryJsonMessage.class, config); + public IChannelProducer newDeliverySendingChannel(String theChannelName, ChannelProducerSettings theChannelSettings) { + ChannelProducerSettings config = newProducerConfigForDeliveryChannel(theChannelSettings); + return myChannelFactory.getOrCreateProducer(theChannelName, ResourceDeliveryJsonMessage.class, config); } - public IChannelReceiver newDeliveryReceivingChannel(String theChannelName, ChannelConsumerSettings theOptions) { - ChannelConsumerSettings config = newConfigForDeliveryChannel(theOptions); - IChannelReceiver channel = myQueueChannelFactory.getOrCreateReceiver(theChannelName, ResourceDeliveryJsonMessage.class, config); + public IChannelReceiver newDeliveryReceivingChannel(String theChannelName, ChannelConsumerSettings theChannelSettings) { + ChannelConsumerSettings config = newConsumerConfigForDeliveryChannel(theChannelSettings); + IChannelReceiver channel = myChannelFactory.getOrCreateReceiver(theChannelName, ResourceDeliveryJsonMessage.class, config); return new BroadcastingSubscribableChannelWrapper(channel); } - public IChannelProducer newMatchingSendingChannel(String theChannelName, ChannelConsumerSettings theOptions) { - ChannelConsumerSettings config = newConfigForMatchingChannel(theOptions); - return myQueueChannelFactory.getOrCreateProducer(theChannelName, ResourceModifiedJsonMessage.class, config); + public IChannelProducer newMatchingSendingChannel(String theChannelName, ChannelProducerSettings theChannelSettings) { + ChannelProducerSettings config = newProducerConfigForMatchingChannel(theChannelSettings); + return myChannelFactory.getOrCreateProducer(theChannelName, ResourceModifiedJsonMessage.class, config); } - public IChannelReceiver newMatchingReceivingChannel(String theChannelName, ChannelConsumerSettings theOptions) { - ChannelConsumerSettings config = newConfigForMatchingChannel(theOptions); - IChannelReceiver channel = myQueueChannelFactory.getOrCreateReceiver(theChannelName, ResourceModifiedJsonMessage.class, config); + public IChannelReceiver newMatchingReceivingChannel(String theChannelName, ChannelConsumerSettings theChannelSettings) { + ChannelConsumerSettings config = newConsumerConfigForMatchingChannel(theChannelSettings); + IChannelReceiver channel = myChannelFactory.getOrCreateReceiver(theChannelName, ResourceModifiedJsonMessage.class, config); return new BroadcastingSubscribableChannelWrapper(channel); } - protected ChannelConsumerSettings newConfigForDeliveryChannel(ChannelConsumerSettings theOptions) { + protected ChannelProducerSettings newProducerConfigForDeliveryChannel(ChannelProducerSettings theOptions) { + ChannelProducerSettings config = new ChannelProducerSettings(); + config.setConcurrentConsumers(getDeliveryChannelConcurrentConsumers()); + return config; + } + + protected ChannelConsumerSettings newConsumerConfigForDeliveryChannel(ChannelConsumerSettings theOptions) { ChannelConsumerSettings config = new ChannelConsumerSettings(); config.setConcurrentConsumers(getDeliveryChannelConcurrentConsumers()); return config; } - protected ChannelConsumerSettings newConfigForMatchingChannel(ChannelConsumerSettings theOptions) { + protected ChannelProducerSettings newProducerConfigForMatchingChannel(ChannelProducerSettings theOptions) { + ChannelProducerSettings config = new ChannelProducerSettings(); + config.setConcurrentConsumers(getMatchingChannelConcurrentConsumers()); + return config; + } + + protected ChannelConsumerSettings newConsumerConfigForMatchingChannel(ChannelConsumerSettings theOptions) { ChannelConsumerSettings config = new ChannelConsumerSettings(); config.setConcurrentConsumers(getMatchingChannelConcurrentConsumers()); return config; @@ -89,6 +101,10 @@ public class SubscriptionChannelFactory { return SubscriptionConstants.MATCHING_CHANNEL_CONCURRENT_CONSUMERS; } + public IChannelFactory getChannelFactory() { + return myChannelFactory; + } + public static class BroadcastingSubscribableChannelWrapper extends AbstractSubscribableChannel implements IChannelReceiver, DisposableBean { private final IChannelReceiver myWrappedChannel; @@ -124,5 +140,9 @@ public class SubscriptionChannelFactory { } + @Override + public String getName() { + return myWrappedChannel.getName(); + } } } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionDeliveryHandlerFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionDeliveryHandlerFactory.java index 15cd802c493..8cba3ed7328 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionDeliveryHandlerFactory.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionDeliveryHandlerFactory.java @@ -20,10 +20,11 @@ package ca.uhn.fhir.jpa.subscription.channel.subscription; * #L% */ -import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; import ca.uhn.fhir.jpa.subscription.match.deliver.email.IEmailSender; import ca.uhn.fhir.jpa.subscription.match.deliver.email.SubscriptionDeliveringEmailSubscriber; +import ca.uhn.fhir.jpa.subscription.match.deliver.message.SubscriptionDeliveringMessageSubscriber; import ca.uhn.fhir.jpa.subscription.match.deliver.resthook.SubscriptionDeliveringRestHookSubscriber; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.messaging.MessageHandler; @@ -44,11 +45,17 @@ public class SubscriptionDeliveryHandlerFactory { return myApplicationContext.getBean(SubscriptionDeliveringRestHookSubscriber.class); } + protected SubscriptionDeliveringMessageSubscriber newSubscriptionDeliveringMessageSubscriber() { + return myApplicationContext.getBean(SubscriptionDeliveringMessageSubscriber.class); + } + public Optional createDeliveryHandler(CanonicalSubscriptionChannelType theChannelType) { if (theChannelType == CanonicalSubscriptionChannelType.EMAIL) { return Optional.of(newSubscriptionDeliveringEmailSubscriber(myEmailSender)); } else if (theChannelType == CanonicalSubscriptionChannelType.RESTHOOK) { return Optional.of(newSubscriptionDeliveringRestHookSubscriber()); + } else if (theChannelType == CanonicalSubscriptionChannelType.MESSAGE) { + return Optional.of(newSubscriptionDeliveringMessageSubscriber()); } else { return Optional.empty(); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/config/SubscriptionProcessorConfig.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/config/SubscriptionProcessorConfig.java index f04c2fc600a..d5e3cb4c875 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/config/SubscriptionProcessorConfig.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/config/SubscriptionProcessorConfig.java @@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryCha import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryHandlerFactory; import ca.uhn.fhir.jpa.subscription.match.deliver.email.IEmailSender; import ca.uhn.fhir.jpa.subscription.match.deliver.email.SubscriptionDeliveringEmailSubscriber; +import ca.uhn.fhir.jpa.subscription.match.deliver.message.SubscriptionDeliveringMessageSubscriber; import ca.uhn.fhir.jpa.subscription.match.deliver.resthook.SubscriptionDeliveringRestHookSubscriber; import ca.uhn.fhir.jpa.subscription.match.matcher.matching.CompositeInMemoryDaoSubscriptionMatcher; import ca.uhn.fhir.jpa.subscription.match.matcher.matching.DaoSubscriptionMatcher; @@ -100,6 +101,12 @@ public class SubscriptionProcessorConfig { return new SubscriptionDeliveringRestHookSubscriber(); } + @Bean + @Scope("prototype") + public SubscriptionDeliveringMessageSubscriber subscriptionDeliveringMessageSubscriber() { + return new SubscriptionDeliveringMessageSubscriber(); + } + @Bean @Scope("prototype") public SubscriptionDeliveringEmailSubscriber subscriptionDeliveringEmailSubscriber(IEmailSender theEmailSender) { diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/message/SubscriptionDeliveringMessageSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/message/SubscriptionDeliveringMessageSubscriber.java new file mode 100644 index 00000000000..9c27cd42584 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/message/SubscriptionDeliveringMessageSubscriber.java @@ -0,0 +1,121 @@ +package ca.uhn.fhir.jpa.subscription.match.deliver.message; + +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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.interceptor.api.HookParams; +import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.subscription.channel.api.ChannelProducerSettings; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.api.IChannelProducer; +import ca.uhn.fhir.jpa.subscription.match.deliver.BaseSubscriptionDeliverySubscriber; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; +import ca.uhn.fhir.rest.api.EncodingEnum; +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.annotation.Scope; +import org.springframework.messaging.MessagingException; + +import java.net.URI; +import java.net.URISyntaxException; + +@Scope("prototype") +public class SubscriptionDeliveringMessageSubscriber extends BaseSubscriptionDeliverySubscriber { + private static Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringMessageSubscriber.class); + + @Autowired + private IChannelFactory myChannelFactory; + + /** + * Constructor + */ + public SubscriptionDeliveringMessageSubscriber() { + super(); + } + + protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, IChannelProducer theChannelProducer) { + IBaseResource payloadResource = theMsg.getPayload(myFhirContext); + + // Regardless of whether we have a payload, the message should be sent. + doDelivery(theMsg, theSubscription, theChannelProducer, payloadResource); + } + + protected void doDelivery(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, IChannelProducer theChannelProducer, IBaseResource thePayloadResource) { + ResourceModifiedMessage payload = new ResourceModifiedMessage(myFhirContext, thePayloadResource, theMsg.getOperationType()); + payload.setParentTransactionGuid(theMsg.getParentTransactionGuid()); + ResourceModifiedJsonMessage message = new ResourceModifiedJsonMessage(payload); + theChannelProducer.send(message); + ourLog.debug("Delivering {} message payload {} for {}", theMsg.getOperationType(), theMsg.getPayloadId(), theSubscription.getIdElement(myFhirContext).toUnqualifiedVersionless().getValue()); + } + + @Override + public void handleMessage(ResourceDeliveryMessage theMessage) throws MessagingException, URISyntaxException { + CanonicalSubscription subscription = theMessage.getSubscription(); + + // Interceptor call: SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY + HookParams params = new HookParams() + .add(CanonicalSubscription.class, subscription) + .add(ResourceDeliveryMessage.class, theMessage); + if (!getInterceptorBroadcaster().callHooks(Pointcut.SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY, params)) { + return; + } + // Grab the endpoint from the subscription + String endpointUrl = subscription.getEndpointUrl(); + + String queueName = extractQueueNameFromEndpoint(endpointUrl); + + ChannelProducerSettings channelSettings = new ChannelProducerSettings(); + channelSettings.setQualifyChannelName(false); + + IChannelProducer channelProducer = myChannelFactory.getOrCreateProducer(queueName, ResourceModifiedJsonMessage.class, channelSettings); + + // Grab the payload type (encoding mimetype) from the subscription + String payloadString = subscription.getPayloadString(); + EncodingEnum payloadType = null; + if (payloadString != null) { + payloadType = EncodingEnum.forContentType(payloadString); + } + + if (payloadType != EncodingEnum.JSON) { + throw new UnsupportedOperationException("Only JSON payload type is currently supported for Message Subscriptions"); + } + + deliverPayload(theMessage, subscription, channelProducer); + + // Interceptor call: SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY + params = new HookParams() + .add(CanonicalSubscription.class, subscription) + .add(ResourceDeliveryMessage.class, theMessage); + if (!getInterceptorBroadcaster().callHooks(Pointcut.SUBSCRIPTION_AFTER_MESSAGE_DELIVERY, params)) { + //noinspection UnnecessaryReturnStatement + return; + } + } + + private String extractQueueNameFromEndpoint(String theEndpointUrl) throws URISyntaxException { + URI uri = new URI(theEndpointUrl); + return uri.getSchemeSpecificPart(); + } +} diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/resthook/SubscriptionDeliveringRestHookSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/resthook/SubscriptionDeliveringRestHookSubscriber.java index ad82286d5a5..cc5dfc428af 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/resthook/SubscriptionDeliveringRestHookSubscriber.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/resthook/SubscriptionDeliveringRestHookSubscriber.java @@ -60,13 +60,11 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; @Scope("prototype") public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDeliverySubscriber { + private static Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringRestHookSubscriber.class); @Autowired private DaoRegistry myDaoRegistry; - private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringRestHookSubscriber.class); - - /** * Constructor */ diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/SubscriptionWebsocketHandler.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/SubscriptionWebsocketHandler.java index dbefcf7249b..548cc6a127d 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/SubscriptionWebsocketHandler.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/deliver/websocket/SubscriptionWebsocketHandler.java @@ -20,10 +20,9 @@ package ca.uhn.fhir.jpa.subscription.match.deliver.websocket; * #L% */ -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelRegistry; import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelWithHandlers; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.IdType; @@ -57,9 +56,6 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement super(); } - @Autowired - private FhirContext myCtx; - private IState myState = new InitialState(); @Override diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/CompositeInMemoryDaoSubscriptionMatcher.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/CompositeInMemoryDaoSubscriptionMatcher.java index 5b16a5400b2..18351b3b5e7 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/CompositeInMemoryDaoSubscriptionMatcher.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/CompositeInMemoryDaoSubscriptionMatcher.java @@ -47,7 +47,6 @@ public class CompositeInMemoryDaoSubscriptionMatcher implements ISubscriptionMat if (myDaoConfig.isEnableInMemorySubscriptionMatching()) { result = myInMemorySubscriptionMatcher.match(theSubscription, theMsg); if (result.supported()) { - // TODO KHS test result.setInMemory(true); } else { ourLog.info("Criteria {} for Subscription {} not supported by InMemoryMatcher: {}. Reverting to DatabaseMatcher", theSubscription.getCriteriaString(), theSubscription.getIdElementString(), result.getUnsupportedReason()); diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/BaseSubscriberForSubscriptionResources.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/BaseSubscriberForSubscriptionResources.java index 1fec3659b80..c54a6b93113 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/BaseSubscriberForSubscriptionResources.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/BaseSubscriberForSubscriptionResources.java @@ -44,7 +44,7 @@ public abstract class BaseSubscriberForSubscriptionResources implements MessageH if (isBlank(payloadIdType)) { IBaseResource payload = theNewResource.getNewPayload(myFhirContext); if (payload != null) { - payloadIdType = myFhirContext.getResourceDefinition(payload).getName(); + payloadIdType = myFhirContext.getResourceType(payload); } } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionMatchingSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionMatchingSubscriber.java index fe96e8f0340..435da9b40a9 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionMatchingSubscriber.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionMatchingSubscriber.java @@ -6,14 +6,14 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelRegistry; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.ISubscriptionMatcher; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryJsonMessage; import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage; import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; -import ca.uhn.fhir.jpa.subscription.match.matcher.matching.ISubscriptionMatcher; -import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; -import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import ca.uhn.fhir.rest.api.EncodingEnum; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -164,6 +164,7 @@ public class SubscriptionMatchingSubscriber implements MessageHandler { deliveryMsg.setPayload(myFhirContext, payload, encoding); deliveryMsg.setSubscription(subscription); deliveryMsg.setOperationType(theMsg.getOperationType()); + deliveryMsg.setParentTransactionGuid(theMsg.getParentTransactionGuid()); deliveryMsg.copyAdditionalPropertiesFrom(theMsg); // Interceptor call: SUBSCRIPTION_RESOURCE_MATCHED diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionConstants.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionConstants.java index c0d2b5860b1..bd30b404216 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionConstants.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionConstants.java @@ -29,7 +29,7 @@ public class SubscriptionConstants { * The number of threads used in subscription channel processing */ public static final int MATCHING_CHANNEL_CONCURRENT_CONSUMERS = 5; - public static final int DELIVERY_CHANNEL_CONCURRENT_CONSUMERS = 5; + public static final int DELIVERY_CHANNEL_CONCURRENT_CONSUMERS = 2; /** * The maximum number of subscriptions that can be active at once diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionLoader.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionLoader.java index 76563312652..062286a82a2 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionLoader.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/SubscriptionLoader.java @@ -21,7 +21,6 @@ package ca.uhn.fhir.jpa.subscription.match.registry; */ import ca.uhn.fhir.jpa.api.dao.DaoRegistry; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.model.sched.HapiJob; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; @@ -54,8 +53,8 @@ public class SubscriptionLoader { private final Object mySyncSubscriptionsLock = new Object(); @Autowired private SubscriptionRegistry mySubscriptionRegistry; - @Autowired(required = false) - private DaoRegistry myDaoRegistry; + @Autowired + DaoRegistry myDaoRegistry; private Semaphore mySyncSubscriptionsSemaphore = new Semaphore(1); @Autowired private ISchedulerService mySchedulerService; @@ -132,8 +131,7 @@ public class SubscriptionLoader { } map.setLoadSynchronousUpTo(SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS); - IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao(); - IBundleProvider subscriptionBundleList = subscriptionDao.search(map); + IBundleProvider subscriptionBundleList = myDaoRegistry.getSubscriptionDao().search(map); Integer subscriptionCount = subscriptionBundleList.size(); assert subscriptionCount != null; diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceDeliveryMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceDeliveryMessage.java index 25b467735f6..9d990f53bca 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceDeliveryMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceDeliveryMessage.java @@ -42,9 +42,19 @@ public class ResourceDeliveryMessage extends BaseResourceMessage implements IRes private transient IBaseResource myPayload; @JsonProperty("payloadId") private String myPayloadId; + @JsonProperty("parentTransactionGuid") + private String myParentTransactionGuid; @JsonProperty("operationType") private ResourceModifiedMessage.OperationTypeEnum myOperationType; + public String getParentTransactionGuid() { + return myParentTransactionGuid; + } + + public void setParentTransactionGuid(String theParentTransactionGuid) { + myParentTransactionGuid = theParentTransactionGuid; + } + /** * Constructor */ diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceModifiedMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceModifiedMessage.java index b72ad8ddc1a..0ddbcb05fff 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceModifiedMessage.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/ResourceModifiedMessage.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.subscription.model; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IModelJson; +import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.util.ResourceReferenceInfo; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; @@ -50,6 +51,8 @@ public class ResourceModifiedMessage extends BaseResourceMessage implements IRes private String myPayload; @JsonProperty("payloadId") private String myPayloadId; + @JsonProperty("parentTransactionGuid") + private String myParentTransactionGuid; @JsonIgnore private transient IBaseResource myPayloadDecoded; @@ -69,6 +72,13 @@ public class ResourceModifiedMessage extends BaseResourceMessage implements IRes } } + public ResourceModifiedMessage(FhirContext theFhirContext, IBaseResource theNewResource, OperationTypeEnum theOperationType, RequestDetails theRequest) { + this(theFhirContext, theNewResource, theOperationType); + if (theRequest != null) { + setParentTransactionGuid(theRequest.getTransactionGuid()); + } + } + @Override public String getPayloadId() { return myPayloadId; @@ -116,6 +126,14 @@ public class ResourceModifiedMessage extends BaseResourceMessage implements IRes } } + public String getParentTransactionGuid() { + return myParentTransactionGuid; + } + + public void setParentTransactionGuid(String theParentTransactionGuid) { + myParentTransactionGuid = theParentTransactionGuid; + } + private void setNewPayload(FhirContext theCtx, IBaseResource theNewPayload) { /* * References with placeholders would be invalid by the time we get here, and diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/config/SubscriptionModelConfig.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/config/SubscriptionModelConfig.java index 4cb80cb599d..863c273f06c 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/config/SubscriptionModelConfig.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/model/config/SubscriptionModelConfig.java @@ -34,11 +34,8 @@ public class SubscriptionModelConfig { return new SubscriptionCanonicalizer(theFhirContext); } - @Bean public SubscriptionStrategyEvaluator subscriptionStrategyEvaluator() { return new SubscriptionStrategyEvaluator(); } - - } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionMatcherInterceptor.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionMatcherInterceptor.java index d8136070cb4..51e01bd6c69 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionMatcherInterceptor.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionMatcherInterceptor.java @@ -8,10 +8,10 @@ import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannel; import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; -import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; -import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; import ca.uhn.fhir.jpa.subscription.match.matcher.matching.IResourceModifiedConsumer; import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionMatchingSubscriber; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.rest.api.server.RequestDetails; import com.google.common.annotations.VisibleForTesting; @@ -95,7 +95,7 @@ public class SubscriptionMatcherInterceptor implements IResourceModifiedConsumer */ @Override public void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType, RequestDetails theRequest) { - ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType); + ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType, theRequest); // Interceptor call: SUBSCRIPTION_RESOURCE_MODIFIED HookParams params = new HookParams() diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java index b140507ff5e..267308f9b4a 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java @@ -25,11 +25,11 @@ import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; -import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy; import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator; import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -38,6 +38,9 @@ import com.google.common.annotations.VisibleForTesting; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; +import java.net.URI; +import java.net.URISyntaxException; + import static org.apache.commons.lang3.StringUtils.isBlank; @Interceptor @@ -68,7 +71,7 @@ public class SubscriptionValidatingInterceptor { } public void validateSubmittedSubscription(IBaseResource theSubscription) { - if (!"Subscription".equals(myFhirContext.getResourceDefinition(theSubscription).getName())) { + if (!"Subscription".equals(myFhirContext.getResourceType(theSubscription))) { return; } @@ -123,8 +126,29 @@ public class SubscriptionValidatingInterceptor { if (subscription.getChannelType() == null) { throw new UnprocessableEntityException("Subscription.channel.type must be populated on this server"); + } else if (subscription.getChannelType() == CanonicalSubscriptionChannelType.MESSAGE) { + validateMessageSubscriptionEndpoint(subscription.getEndpointUrl()); } + } + } + public void validateMessageSubscriptionEndpoint(String theEndpointUrl) { + if (theEndpointUrl == null) { + throw new UnprocessableEntityException("No endpoint defined for message subscription"); + } + + try { + URI uri = new URI(theEndpointUrl); + + if (!"channel".equals(uri.getScheme())) { + throw new UnprocessableEntityException("Only 'channel' protocol is supported for Subscriptions with channel type 'message'"); + } + String channelName = uri.getSchemeSpecificPart(); + if (isBlank(channelName)) { + throw new UnprocessableEntityException("A channel name must appear after channel: in a message Subscription endpoint"); + } + } catch (URISyntaxException e) { + throw new UnprocessableEntityException("Invalid subscription endpoint uri " + theEndpointUrl, e); } } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/triggering/SubscriptionTriggeringSvcImpl.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/triggering/SubscriptionTriggeringSvcImpl.java index 84c35ab01ab..36723118bfb 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/triggering/SubscriptionTriggeringSvcImpl.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/triggering/SubscriptionTriggeringSvcImpl.java @@ -32,8 +32,8 @@ import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; import ca.uhn.fhir.jpa.subscription.match.matcher.matching.IResourceModifiedConsumer; +import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.api.CacheControlDirective; @@ -74,7 +74,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import static ca.uhn.fhir.jpa.model.util.ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID; +import static ca.uhn.fhir.rest.server.provider.ProviderConstants.SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactoryTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactoryTest.java index 51d300fb3bb..1e77adb5db2 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactoryTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/channel/subscription/SubscriptionChannelFactoryTest.java @@ -31,12 +31,15 @@ public class SubscriptionChannelFactoryTest { @Mock private ChannelInterceptor myInterceptor; + @Mock + private IChannelNamer myChannelNamer; @Captor private ArgumentCaptor myExceptionCaptor; @Before public void before() { - mySvc = new SubscriptionChannelFactory(new LinkedBlockingChannelFactory()); + when(myChannelNamer.getChannelName(any(), any())).thenReturn("CHANNEL_NAME"); + mySvc = new SubscriptionChannelFactory(new LinkedBlockingChannelFactory(myChannelNamer)); } /** 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 index c7b0369d2c6..0291269bb6f 100644 --- 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 @@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.subscription.IChannelNamer; import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSearchParamProvider; @@ -56,8 +57,8 @@ public abstract class BaseSubscriptionTest { } @Bean - public SubscriptionChannelFactory mySubscriptionChannelFactory() { - return new SubscriptionChannelFactory(new LinkedBlockingChannelFactory()); + public SubscriptionChannelFactory mySubscriptionChannelFactory(IChannelNamer theChannelNamer) { + return new SubscriptionChannelFactory(new LinkedBlockingChannelFactory(theChannelNamer)); } @Bean @@ -65,6 +66,10 @@ public abstract class BaseSubscriptionTest { return new InterceptorService(); } - + @Bean + // Default implementation returns the name unchanged + public IChannelNamer channelNamer() { + return (theNameComponent, theChannelSettings) -> theNameComponent; + } } } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionTestConfig.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionTestConfig.java index 9ea109593bf..0ea13ede15e 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionTestConfig.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionTestConfig.java @@ -25,6 +25,7 @@ import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig; import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory; import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannelFactory; +import ca.uhn.fhir.jpa.subscription.channel.subscription.IChannelNamer; import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; @@ -41,6 +42,8 @@ public class SubscriptionTestConfig { @Autowired private FhirContext myFhirContext; + @Autowired + private IChannelNamer myChannelNamer; @Primary @Bean(autowire = Autowire.BY_NAME, name = "myJpaValidationSupportChain") @@ -50,11 +53,11 @@ public class SubscriptionTestConfig { @Bean public IChannelFactory subscribableChannelFactory() { - return new LinkedBlockingChannelFactory(); + return new LinkedBlockingChannelFactory(myChannelNamer); } @Bean - public SubscriptionChannelFactory subscriptionChannelFactory(IChannelFactory theQueueChannelFactory) { + public SubscriptionChannelFactory subscriptionChannelFactory(IChannelNamer theChannelNamer, IChannelFactory theQueueChannelFactory) { return new SubscriptionChannelFactory(theQueueChannelFactory); } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoaderTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoaderTest.java index 845dadbc40e..bafc3727589 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoaderTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoaderTest.java @@ -8,7 +8,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class SubscriptionLoaderTest extends BaseBlockingQueueSubscribableChannelDstu3Test { - @Test public void testMultipleThreadsDontBlock() throws InterruptedException { SubscriptionLoader svc = new SubscriptionLoader(); @@ -25,5 +24,4 @@ public class SubscriptionLoaderTest extends BaseBlockingQueueSubscribableChannel latch.await(10, TimeUnit.SECONDS); svc.syncSubscriptions(); } - } diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelRegistryTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelRegistryTest.java index 57c4e2ab888..e45dfa473cf 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelRegistryTest.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionChannelRegistryTest.java @@ -2,11 +2,11 @@ package ca.uhn.fhir.jpa.subscription.module.channel; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.subscription.channel.api.IChannelProducer; -import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; -import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelFactory; import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionChannelRegistry; import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryHandlerFactory; +import ca.uhn.fhir.jpa.subscription.match.registry.ActiveSubscription; +import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription; import ca.uhn.fhir.model.primitive.IdDt; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,6 +27,7 @@ public class SubscriptionChannelRegistryTest { private static final String TEST_CHANNEL_NAME = "TEST_CHANNEL"; @Autowired SubscriptionChannelRegistry mySubscriptionChannelRegistry; + @MockBean SubscriptionDeliveryHandlerFactory mySubscriptionDeliveryHandlerFactory; @MockBean diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java index c1f6a7de1a8..13d26edd23d 100644 --- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseBlockingQueueSubscribableChannelDstu3Test.java @@ -131,11 +131,6 @@ public abstract class BaseBlockingQueueSubscribableChannelDstu3Test extends Base return theResource; } - protected void initSubscriptionLoader(List subscriptions, String uuid) throws InterruptedException { -// myMockFhirClientSubscriptionProvider.setBundleProvider(new SimpleBundleProvider(new ArrayList<>(subscriptions), uuid)); - mySubscriptionLoader.doSyncSubscriptionsForUnitTest(); - } - protected Subscription sendSubscription(String theCriteria, String thePayload, String theEndpoint) throws InterruptedException { Subscription subscription = makeActiveSubscription(theCriteria, thePayload, theEndpoint); mySubscriptionActivatedPost.setExpectedCount(1); diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptorTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptorTest.java new file mode 100644 index 00000000000..66a4a8acb52 --- /dev/null +++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptorTest.java @@ -0,0 +1,157 @@ +package ca.uhn.fhir.jpa.subscription.submit.interceptor; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator; +import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import org.hl7.fhir.r4.model.Subscription; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@RunWith(SpringRunner.class) + +public class SubscriptionValidatingInterceptorTest { + @Autowired + private SubscriptionValidatingInterceptor mySubscriptionValidatingInterceptor; + @MockBean + private DaoRegistry myDaoRegistry; + @MockBean + private SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator; + + @Configuration + public static class SpringConfig { + @Bean + FhirContext fhirContext() { + return FhirContext.forR4(); + } + + @Bean + SubscriptionValidatingInterceptor subscriptionValidatingInterceptor() { + return new SubscriptionValidatingInterceptor(); + } + + @Bean + SubscriptionCanonicalizer subscriptionCanonicalizer(FhirContext theFhirContext) { + return new SubscriptionCanonicalizer(theFhirContext); + } + } + + @Before + public void before() { + when(myDaoRegistry.isResourceTypeSupported(any())).thenReturn(true); + } + + @Test + public void testEmptySub() { + try { + Subscription badSub = new Subscription(); + mySubscriptionValidatingInterceptor.validateSubmittedSubscription(badSub); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), is("Can not process submitted Subscription - Subscription.status must be populated on this server")); + } + } + + @Test + public void testEmptyStatus() { + try { + Subscription badSub = new Subscription(); + badSub.setStatus(Subscription.SubscriptionStatus.ACTIVE); + mySubscriptionValidatingInterceptor.validateSubmittedSubscription(badSub); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), is("Subscription.criteria must be populated")); + } + } + + @Test + public void testBadCriteria() { + try { + Subscription badSub = new Subscription(); + badSub.setStatus(Subscription.SubscriptionStatus.ACTIVE); + badSub.setCriteria("Patient"); + mySubscriptionValidatingInterceptor.validateSubmittedSubscription(badSub); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), is("Subscription.criteria must be in the form \"{Resource Type}?[params]\"")); + } + } + + @Test + public void testBadChannel() { + try { + Subscription badSub = new Subscription(); + badSub.setStatus(Subscription.SubscriptionStatus.ACTIVE); + badSub.setCriteria("Patient?"); + mySubscriptionValidatingInterceptor.validateSubmittedSubscription(badSub); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), is("Subscription.channel.type must be populated")); + } + } + + @Test + public void testEmptyEndpoint() { + try { + Subscription badSub = new Subscription(); + badSub.setStatus(Subscription.SubscriptionStatus.ACTIVE); + badSub.setCriteria("Patient?"); + Subscription.SubscriptionChannelComponent channel = badSub.getChannel(); + channel.setType(Subscription.SubscriptionChannelType.MESSAGE); + mySubscriptionValidatingInterceptor.validateSubmittedSubscription(badSub); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), is("No endpoint defined for message subscription")); + } + } + + @Test + public void testMalformedEndpoint() { + Subscription badSub = new Subscription(); + badSub.setStatus(Subscription.SubscriptionStatus.ACTIVE); + badSub.setCriteria("Patient?"); + Subscription.SubscriptionChannelComponent channel = badSub.getChannel(); + channel.setType(Subscription.SubscriptionChannelType.MESSAGE); + + channel.setEndpoint("foo"); + try { + mySubscriptionValidatingInterceptor.validateSubmittedSubscription(badSub); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), is("Only 'channel' protocol is supported for Subscriptions with channel type 'message'")); + } + + channel.setEndpoint("channel"); + try { + mySubscriptionValidatingInterceptor.validateSubmittedSubscription(badSub); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), is("Only 'channel' protocol is supported for Subscriptions with channel type 'message'")); + } + + channel.setEndpoint("channel:"); + try { + mySubscriptionValidatingInterceptor.validateSubmittedSubscription(badSub); + fail(); + } catch (UnprocessableEntityException e) { + assertThat(e.getMessage(), is("Invalid subscription endpoint uri channel:")); + } + + // Happy path + channel.setEndpoint("channel:my-queue-name"); + mySubscriptionValidatingInterceptor.validateSubmittedSubscription(badSub); + } +} diff --git a/hapi-fhir-jpaserver-test-utilities/pom.xml b/hapi-fhir-jpaserver-test-utilities/pom.xml new file mode 100644 index 00000000000..2c57d9ec6dd --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + ca.uhn.hapi.fhir + hapi-deployable-pom + 5.1.0-SNAPSHOT + ../hapi-deployable-pom/pom.xml + + + jar + + HAPI FHIR JPA Server Test Utilities + hapi-fhir-jpaserver-test-utilities + + + ca.uhn.hapi.fhir + hapi-fhir-test-utilities + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-base + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-test-utilities + ${project.version} + + + ch.qos.logback + logback-classic + + + org.springframework + spring-test + + + com.h2database + h2 + 1.4.199 + + + org.apache.commons + commons-dbcp2 + + + commons-logging + commons-logging + + + + + junit + junit + + + diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/config/TestJpaConfig.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/config/TestJpaConfig.java new file mode 100644 index 00000000000..b82166f8301 --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/config/TestJpaConfig.java @@ -0,0 +1,54 @@ +package ca.uhn.fhir.jpa.config; + +/*- + * #%L + * HAPI FHIR JPA Server Test Utilities + * %% + * Copyright (C) 2014 - 2020 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.api.config.DaoConfig; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.orm.jpa.JpaTransactionManager; + +import javax.persistence.EntityManagerFactory; +// TODO RC1 can use in jpa tests? +public class TestJpaConfig { + @Bean + public DaoConfig daoConfig() { + DaoConfig daoConfig = new DaoConfig(); + return daoConfig; + } + + @Bean + public ModelConfig modelConfig() { + return daoConfig().getModelConfig(); + } + + @Bean + public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { + JpaTransactionManager retVal = new JpaTransactionManager(); + retVal.setEntityManagerFactory(entityManagerFactory); + return retVal; + } + + @Bean + public PartitionSettings partitionSettings() { + return new PartitionSettings(); + } +} diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/config/TestJpaR4Config.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/config/TestJpaR4Config.java new file mode 100644 index 00000000000..f25eef9754f --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/config/TestJpaR4Config.java @@ -0,0 +1,138 @@ +package ca.uhn.fhir.jpa.config; + +/*- + * #%L + * HAPI FHIR JPA Server Test Utilities + * %% + * Copyright (C) 2014 - 2020 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.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc; +import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl; +import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener; +import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener; +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.hibernate.dialect.H2Dialect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Lazy; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; + +import javax.sql.DataSource; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +@Import(TestJpaConfig.class) +public class TestJpaR4Config extends BaseJavaConfigR4 { + private static final Logger ourLog = LoggerFactory.getLogger(TestJpaR4Config.class); + @Autowired + FhirContext myFhirContext; + + @Bean + public CircularQueueCaptureQueriesListener captureQueriesListener() { + return new CircularQueueCaptureQueriesListener(); + } + + @Bean + public DataSource dataSource() { + BasicDataSource retVal = new BasicDataSource(); + + retVal.setDriver(new org.h2.Driver()); + retVal.setUrl("jdbc:h2:mem:testdb_r4"); + retVal.setMaxWaitMillis(10000); + retVal.setUsername(""); + retVal.setPassword(""); + + SLF4JLogLevel level = SLF4JLogLevel.INFO; + DataSource dataSource = ProxyDataSourceBuilder + .create(retVal) +// .logQueryBySlf4j(level, "SQL") + .logSlowQueryBySlf4j(10, TimeUnit.SECONDS) +// .countQuery(new ThreadQueryCountHolder()) +// .beforeQuery(new BlockLargeNumbersOfParamsListener()) + .afterQuery(captureQueriesListener()) + .afterQuery(new CurrentThreadCaptureQueriesListener()) + .countQuery(singleQueryCountHolder()) + .build(); + + return dataSource; + } + + @Bean + public SingleQueryCountHolder singleQueryCountHolder() { + return new SingleQueryCountHolder(); + } + + @Override + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); + retVal.setPersistenceUnitName("PU_HapiFhirJpaR4"); + retVal.setDataSource(dataSource()); + retVal.setJpaProperties(jpaProperties()); + return retVal; + } + + @Bean + public Properties jpaProperties() { + Properties extraProperties = new Properties(); + extraProperties.put("hibernate.format_sql", "false"); + extraProperties.put("hibernate.show_sql", "false"); + extraProperties.put("hibernate.hbm2ddl.auto", "update"); + extraProperties.put("hibernate.dialect", H2Dialect.class.getName()); + extraProperties.put("hibernate.search.model_mapping", ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory.class.getName()); + extraProperties.put("hibernate.search.default.directory_provider", "local-heap"); + extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); + extraProperties.put("hibernate.search.autoregister_listeners", "true"); + + return extraProperties; + } + + /** + * Bean which validates incoming requests + */ + @Bean + @Lazy + public RequestValidatingInterceptor requestValidatingInterceptor() { + RequestValidatingInterceptor requestValidator = new RequestValidatingInterceptor(); + requestValidator.setFailOnSeverity(ResultSeverityEnum.ERROR); + requestValidator.setAddResponseHeaderOnSeverity(null); + requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION); + requestValidator.addValidatorModule(instanceValidator()); + + return requestValidator; + } + + @Bean + public IBinaryStorageSvc binaryStorage() { + return new MemoryBinaryStorageSvcImpl(); + } + + @Bean + public DefaultProfileValidationSupport validationSupportChainR4() { + return new DefaultProfileValidationSupport(myFhirContext); + } +} diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java new file mode 100644 index 00000000000..acb5f6079a1 --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java @@ -0,0 +1,29 @@ +package ca.uhn.fhir.jpa.test; + +/*- + * #%L + * HAPI FHIR JPA Server Test Utilities + * %% + * Copyright (C) 2014 - 2020 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.config.TestJpaR4Config; +import org.springframework.test.context.ContextConfiguration; + +@ContextConfiguration(classes = {TestJpaR4Config.class}) +public abstract class BaseJpaR4Test extends BaseJpaTest { + +} diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaTest.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaTest.java new file mode 100644 index 00000000000..f1f3ef51e38 --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaTest.java @@ -0,0 +1,100 @@ +package ca.uhn.fhir.jpa.test; + +/*- + * #%L + * HAPI FHIR JPA Server Test Utilities + * %% + * Copyright (C) 2014 - 2020 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.dao.expunge.ExpungeEverythingService; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor; +import ca.uhn.fhir.util.TestUtil; +import org.junit.After; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +import java.util.concurrent.Callable; + +@TestPropertySource(properties = { + // Since scheduled tasks can cause searches, which messes up the + // value returned by SearchBuilder.getLastHandlerMechanismForUnitTest() + UnregisterScheduledProcessor.SCHEDULING_DISABLED_EQUALS_TRUE +}) +@RunWith(SpringRunner.class) +public abstract class BaseJpaTest { + private static final Logger ourLog = LoggerFactory.getLogger(BaseJpaTest.class); + + static { + System.setProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS, "1000"); + System.setProperty("test", "true"); + System.setProperty("unit_test_mode", "true"); + TestUtil.setShouldRandomizeTimezones(false); + } + + @Autowired + protected ExpungeEverythingService myExpungeEverythingService; + + @Autowired + PlatformTransactionManager myPlatformTransactionManager; + + @Autowired + ApplicationContext myApplicationContext; + + @After + public void after() { + ourLog.info("\n --- @After ---"); + myExpungeEverythingService.expungeEverything(null); + } + + public TransactionTemplate newTxTemplate() { + TransactionTemplate retVal = new TransactionTemplate(myPlatformTransactionManager); + retVal.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + retVal.afterPropertiesSet(); + return retVal; + } + + public void runInTransaction(Runnable theRunnable) { + newTxTemplate().execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus theStatus) { + theRunnable.run(); + } + }); + } + + public T runInTransaction(Callable theRunnable) { + return newTxTemplate().execute(t -> { + try { + return theRunnable.call(); + } catch (Exception theE) { + throw new InternalErrorException(theE); + } + }); + } +} 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 e01782d6165..552891cfc47 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,7 @@ import ca.uhn.fhir.jpa.provider.r5.JpaConformanceProviderR5; import ca.uhn.fhir.jpa.provider.r5.JpaSystemProviderR5; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; +import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.ETagSupportEnum; @@ -32,6 +32,7 @@ import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor; import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; import ca.uhn.fhir.rest.server.interceptor.FhirPathFilterInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; +import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import ca.uhn.fhirtest.config.TestDstu2Config; import ca.uhn.fhirtest.config.TestDstu3Config; import ca.uhn.fhirtest.config.TestR4Config; diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java index a2361eb5def..b6f025f13a4 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; +import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java index 5ec3683e1d8..872ac53a597 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java @@ -1,7 +1,7 @@ package ca.uhn.fhirtest.config; -import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect; diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java index 2a6764848ec..478233a5c59 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java @@ -1,7 +1,7 @@ package ca.uhn.fhirtest.config; -import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java index 8b2340d1075..bbc57696353 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java @@ -1,7 +1,7 @@ package ca.uhn.fhirtest.config; -import ca.uhn.fhir.jpa.config.BaseJavaConfigR4; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.config.BaseJavaConfigR4; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java index 8139e43ff29..d57e87ac8b5 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java @@ -1,7 +1,7 @@ package ca.uhn.fhirtest.config; -import ca.uhn.fhir.jpa.config.BaseJavaConfigR5; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.config.BaseJavaConfigR5; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java b/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java index 64daba67483..f83313f167e 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java @@ -1,15 +1,16 @@ package ca.uhn.fhirtest; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppContext; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; -import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; -import org.hl7.fhir.instance.model.api.IIdType; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.api.IGenericClient; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.webapp.WebAppContext; +import org.hl7.fhir.dstu3.model.Organization; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Subscription; +import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; +import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; +import org.hl7.fhir.instance.model.api.IIdType; public class UhnFhirTestApp { @@ -18,7 +19,7 @@ public class UhnFhirTestApp { public static void main(String[] args) throws Exception { int myPort = 8889; - String base = "http://localhost:" + myPort + "/baseDstu2"; + String base = "http://localhost:" + myPort + "/baseR4"; // new File("target/testdb").mkdirs(); System.setProperty("fhir.db.location", "./target/testdb"); diff --git a/hapi-fhir-server-empi/pom.xml b/hapi-fhir-server-empi/pom.xml new file mode 100644 index 00000000000..27a2e55245a --- /dev/null +++ b/hapi-fhir-server-empi/pom.xml @@ -0,0 +1,124 @@ + + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-deployable-pom + 5.1.0-SNAPSHOT + ../hapi-deployable-pom/pom.xml + + + hapi-fhir-server-empi + jar + + HAPI FHIR - Enterprise Master Patient Index + + + + ca.uhn.hapi.fhir + hapi-fhir-base + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-server + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu3 + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-structures-r4 + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-structures-r5 + ${project.version} + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + info.debatty + java-string-similarity + 1.2.1 + + + org.springframework + spring-beans + ${spring_version} + + + org.springframework + spring-context + ${spring_version} + + + javax.annotation + javax.annotation-api + + + + ch.qos.logback + logback-classic + test + + + org.springframework + spring-test + test + + + org.springframework.boot + spring-boot-test + ${spring_boot_version} + test + + + com.github.ben-manes.caffeine + caffeine + test + + + + + + + + + org.apache.maven.plugins + maven-site-plugin + + true + + + + + + + org.jacoco + jacoco-maven-plugin + + + default-prepare-agent + + prepare-agent + + + + + + + diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiConstants.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiConstants.java new file mode 100644 index 00000000000..4b3a134f124 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiConstants.java @@ -0,0 +1,36 @@ +package ca.uhn.fhir.empi.api; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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 class EmpiConstants { + /** + * TAG system for Person resources which are managed by HAPI EMPI. + */ + + public static final String SYSTEM_EMPI_MANAGED = "https://hapifhir.org/NamingSystem/managing-empi-system"; + public static final String CODE_HAPI_EMPI_MANAGED = "HAPI-EMPI"; + public static final String DISPLAY_HAPI_EMPI_MANAGED = "This Person can only be modified by Smile CDR's EMPI system."; + public static final String CODE_NO_EMPI_MANAGED = "NO-EMPI"; + public static final String HAPI_ENTERPRISE_IDENTIFIER_SYSTEM = "http://hapifhir.io/fhir/NamingSystem/empi-person-enterprise-id"; + public static final String ALL_RESOURCE_SEARCH_PARAM_TYPE = "*"; + + +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiLinkSourceEnum.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiLinkSourceEnum.java new file mode 100644 index 00000000000..f763896f0a5 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiLinkSourceEnum.java @@ -0,0 +1,36 @@ +package ca.uhn.fhir.empi.api; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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 enum EmpiLinkSourceEnum { + /** + * Link was created or last modified by an algorithm + */ + AUTO, + + /** + * Link was created or last modified by a person + */ + + MANUAL + + // Stored in database as ORDINAL. Only add new values to bottom! +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchResultEnum.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchResultEnum.java new file mode 100644 index 00000000000..b58987118cd --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchResultEnum.java @@ -0,0 +1,45 @@ +package ca.uhn.fhir.empi.api; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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 enum EmpiMatchResultEnum { + /** + * Manually confirmed to not be a match. + */ + NO_MATCH, + + /** + * Enough of a match to warrant manual review. + */ + POSSIBLE_MATCH, + + /** + * Strong enough match to consider matched. + */ + MATCH, + + /** + * Link between two Person resources indicating they may be duplicates. + */ + POSSIBLE_DUPLICATE + + // Stored in database as ORDINAL. Only add new values to bottom! +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkQuerySvc.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkQuerySvc.java new file mode 100644 index 00000000000..778ead37836 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkQuerySvc.java @@ -0,0 +1,31 @@ +package ca.uhn.fhir.empi.api; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.model.EmpiTransactionContext; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IIdType; + +public interface IEmpiLinkQuerySvc { + IBaseParameters queryLinks(IIdType thePersonId, IIdType theTargetId, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiContext); + + IBaseParameters getPossibleDuplicates(EmpiTransactionContext theEmpiContext); +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkSvc.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkSvc.java new file mode 100644 index 00000000000..0289145c852 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkSvc.java @@ -0,0 +1,52 @@ +package ca.uhn.fhir.empi.api; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.model.EmpiTransactionContext; +import org.hl7.fhir.instance.model.api.IAnyResource; + +public interface IEmpiLinkSvc { + + /** + * Update a link between a Person record and its target Patient/Practitioner record. If a link does not exist between + * these two records, create it. + * @param thePerson the Person to link the target resource to. + * @param theTargetResource the target resource, which is a Patient or Practitioner + * @param theMatchResult the current status of the match to set the link to. + * @param theLinkSource MANUAL or AUTO: what caused the link. + * @param theEmpiTransactionContext + */ + void updateLink(IAnyResource thePerson, IAnyResource theTargetResource, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiTransactionContext); + + /** + * Replace Person.link values from what they should be based on EmpiLink values + * @param thePersonResource the person to correct the links on. + */ + void syncEmpiLinksToPersonLinks(IAnyResource thePersonResource, EmpiTransactionContext theEmpiTransactionContext); + + /** + * Delete a link between given Person and target patient/practitioner + * @param theExistingPerson + * @param theResource + */ + void deleteLink(IAnyResource theExistingPerson, IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext); + +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkUpdaterSvc.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkUpdaterSvc.java new file mode 100644 index 00000000000..956e6ff0c06 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkUpdaterSvc.java @@ -0,0 +1,28 @@ +package ca.uhn.fhir.empi.api; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.model.EmpiTransactionContext; +import org.hl7.fhir.instance.model.api.IAnyResource; + +public interface IEmpiLinkUpdaterSvc { + IAnyResource updateLink(IAnyResource thePerson, IAnyResource theTarget, EmpiMatchResultEnum theMatchResult, EmpiTransactionContext theEmpiContext); +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiMatchFinderSvc.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiMatchFinderSvc.java new file mode 100644 index 00000000000..9a9e46adf7f --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiMatchFinderSvc.java @@ -0,0 +1,52 @@ +package ca.uhn.fhir.empi.api; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import javax.annotation.Nonnull; +import java.util.List; + +public interface IEmpiMatchFinderSvc { + /** + * Retrieve a list of possible Patient/Practitioner candidates for matching, based on the given {@link IBaseResource} + * Internally, performs all EMPI matching rules on the type of the resource. + * + * @param theResourceType the type of the resource. + * @param theResource the resource that we are attempting to find matches for. + * @return a List of {@link MatchedTarget} representing POSSIBLE_MATCH and MATCH outcomes. + */ + @Nonnull + List getMatchedTargets(String theResourceType, IAnyResource theResource); + + /** + * Used by the $match operation. + * Retrieve a list of Patient/Practitioner matches, based on the given {@link IAnyResource} + * Internally, performs all EMPI matching rules on the type of the resource then returns only those + * with a match result of MATCHED. + * + * @param theResourceType the type of the resource. + * @param theResource the resource that we are attempting to find matches for. + * @return a List of {@link IAnyResource} representing all people who had a MATCH outcome. + */ + List findMatches(String theResourceType, IAnyResource theResource); +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiPersonMergerSvc.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiPersonMergerSvc.java new file mode 100644 index 00000000000..380899ba524 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiPersonMergerSvc.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.empi.api; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.model.EmpiTransactionContext; +import org.hl7.fhir.instance.model.api.IAnyResource; + +public interface IEmpiPersonMergerSvc { + /** + * Move all links from the thePersonToDelete to thePersonToKeep and then delete thePersonToDelete. Merge all Person + * fields, with fields in thePersonToKeep overriding fields in thePersonToDelete + * @param thePersonToDelete the person we are merging from + * @param thePersonToKeep the person we are merging to + * @return updated thePersonToKeep with the merged fields and links. + */ + IAnyResource mergePersons(IAnyResource thePersonToDelete, IAnyResource thePersonToKeep, EmpiTransactionContext theEmpiTransactionContext); +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiSettings.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiSettings.java new file mode 100644 index 00000000000..eb891da6980 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiSettings.java @@ -0,0 +1,38 @@ +package ca.uhn.fhir.empi.api; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.rules.json.EmpiRulesJson; + +public interface IEmpiSettings { + String EMPI_CHANNEL_NAME = "empi"; + int EMPI_DEFAULT_CONCURRENT_CONSUMERS = 5; + + boolean isEnabled(); + + int getConcurrentConsumers(); + + EmpiRulesJson getEmpiRules(); + + boolean isPreventEidUpdates(); + + boolean isPreventMultipleEids(); +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/MatchedTarget.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/MatchedTarget.java new file mode 100644 index 00000000000..1b36e61506b --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/MatchedTarget.java @@ -0,0 +1,50 @@ +package ca.uhn.fhir.empi.api; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.hl7.fhir.instance.model.api.IAnyResource; + +public class MatchedTarget { + + private final IAnyResource myTarget; + private final EmpiMatchResultEnum myMatchResult; + + public MatchedTarget(IAnyResource theTarget, EmpiMatchResultEnum theMatchResult) { + myTarget = theTarget; + myMatchResult = theMatchResult; + } + + public IAnyResource getTarget() { + return myTarget; + } + + public EmpiMatchResultEnum getMatchResult() { + return myMatchResult; + } + + public boolean isMatch() { + return myMatchResult == EmpiMatchResultEnum.MATCH; + } + + public boolean isPossibleMatch() { + return myMatchResult == EmpiMatchResultEnum.POSSIBLE_MATCH; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/log/Logs.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/log/Logs.java new file mode 100644 index 00000000000..77f4215b1ec --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/log/Logs.java @@ -0,0 +1,32 @@ +package ca.uhn.fhir.empi.log; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Logs { + private static final Logger ourEmpiTroubleshootingLog = LoggerFactory.getLogger("ca.uhn.fhir.log.empi_troubleshooting"); + + public static Logger getEmpiTroubleshootingLog() { + return ourEmpiTroubleshootingLog; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/model/CanonicalEID.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/model/CanonicalEID.java new file mode 100644 index 00000000000..a9d1052f0d4 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/model/CanonicalEID.java @@ -0,0 +1,139 @@ +package ca.uhn.fhir.empi.model; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.fhirpath.IFhirPath; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.Identifier; + +import java.util.List; +import java.util.stream.Collectors; + +public class CanonicalEID { + + private String mySystem; + private String myUse; + private String myValue; + + public CanonicalEID(String theSystem, String theValue, String theUse){ + mySystem = theSystem; + myUse = theUse; + myValue = theValue; + } + + /** + * Constructor is private as we expect you to use the factory method. + */ + @SuppressWarnings("rawtypes") + private CanonicalEID(IFhirPath theFhirPath, IBase theIBase) { + List value = theFhirPath.evaluate(theIBase, "value", IPrimitiveType.class); + List system = theFhirPath.evaluate(theIBase, "system", IPrimitiveType.class); + List use = theFhirPath.evaluate(theIBase, "use", IPrimitiveType.class); + + myUse = use.isEmpty() ? null : use.get(0).getValueAsString(); + myValue = value.isEmpty() ? null : value.get(0).getValueAsString(); + mySystem = system.isEmpty() ? null : system.get(0).getValueAsString(); + } + + /** + * Get the appropriate FHIRPath expression to extract the EID identifier value, regardless of resource type. + * e.g. if theBaseResource is a patient, and the EMPI EID system is test-system, this will return + * + * Patient.identifier.where(system='test-system').value + * + */ + private static String buildEidFhirPath(FhirContext theFhirContext, String theEidSystem, IBaseResource theBaseResource) { + return theFhirContext.getResourceType(theBaseResource) + + ".identifier.where(system='" + + theEidSystem + + "')"; + } + + public Identifier toR4() { + return new Identifier() + .setUse(Identifier.IdentifierUse.fromCode(myUse)) + .setSystem(mySystem) + .setValue(myValue); + } + + public org.hl7.fhir.dstu3.model.Identifier toDSTU3(){ + return new org.hl7.fhir.dstu3.model.Identifier() + .setUse(org.hl7.fhir.dstu3.model.Identifier.IdentifierUse.fromCode(myUse)) + .setSystem(mySystem) + .setValue(myValue); + } + + public String getSystem() { + return mySystem; + } + + public String getUse() { + return myUse; + } + + public String getValue() { + return myValue; + } + + public void setSystem(String theSystem) { + mySystem = theSystem; + } + + public void setUse(String theUse) { + myUse = theUse; + } + + private void setValue(String theValue) { + myValue = theValue; + } + + @Override + public String toString() { + return "CanonicalEID{" + + "mySystem='" + mySystem + '\'' + + ", myUse='" + myUse + '\'' + + ", myValue='" + myValue + '\'' + + '}'; + } + + + /** + * A Factory method to generate a {@link CanonicalEID} object from an incoming resource. + * + * @param theFhirContext the {@link FhirContext} of the application, used to generate a FHIRPath parser. + * @param theEidSystem the enterprise identifier system URI used by this instance. + * @param theBaseResource the {@link IBaseResource} from which you would like to extract EIDs. + * + * @return an optional {@link CanonicalEID} object, representing a resource identifier that matched the given eidSystem. + */ + public static List extractFromResource(FhirContext theFhirContext, String theEidSystem, IBaseResource theBaseResource) { + IFhirPath fhirPath = theFhirContext.newFhirPath(); + String eidPath = buildEidFhirPath(theFhirContext, theEidSystem, theBaseResource); + List evaluate = fhirPath.evaluate(theBaseResource, eidPath, IBase.class); + + return evaluate.stream() + .map(ibase -> new CanonicalEID(fhirPath, ibase)) + .collect(Collectors.toList()); + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/model/CanonicalIdentityAssuranceLevel.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/model/CanonicalIdentityAssuranceLevel.java new file mode 100644 index 00000000000..a619ff14c0d --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/model/CanonicalIdentityAssuranceLevel.java @@ -0,0 +1,44 @@ +package ca.uhn.fhir.empi.model; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.hl7.fhir.r4.model.Person; + +public enum CanonicalIdentityAssuranceLevel { + LEVEL1("level1"), + LEVEL2("level2"), + LEVEL3("level3"), + LEVEL4("level4"); + + private String myCanonicalLevel; + private CanonicalIdentityAssuranceLevel(String theCanonicalLevel) { + myCanonicalLevel = theCanonicalLevel; + } + + public Person.IdentityAssuranceLevel toR4() { + return Person.IdentityAssuranceLevel.fromCode(myCanonicalLevel); + } + + public org.hl7.fhir.dstu3.model.Person.IdentityAssuranceLevel toDstu3() { + return org.hl7.fhir.dstu3.model.Person.IdentityAssuranceLevel.fromCode(myCanonicalLevel); + } + +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/model/EmpiTransactionContext.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/model/EmpiTransactionContext.java new file mode 100644 index 00000000000..de88e6913d9 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/model/EmpiTransactionContext.java @@ -0,0 +1,71 @@ +package ca.uhn.fhir.empi.model; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.rest.server.TransactionLogMessages; + +public class EmpiTransactionContext { + + /** + * Any EMPI methods may add transaction log messages. + */ + private TransactionLogMessages myTransactionLogMessages; + + private OperationType myRestOperation; + + + public enum OperationType { + CREATE, + UPDATE, + BATCH, + MERGE_PERSONS + } + public TransactionLogMessages getTransactionLogMessages() { + return myTransactionLogMessages; + } + + public EmpiTransactionContext() { + } + + public EmpiTransactionContext(TransactionLogMessages theTransactionLogMessages, OperationType theRestOperation) { + myTransactionLogMessages = theTransactionLogMessages; + myRestOperation = theRestOperation; + } + + public void addTransactionLogMessage(String theMessage) { + if (myTransactionLogMessages == null) { + return; + } + this.myTransactionLogMessages.addMessage(myTransactionLogMessages, theMessage); + } + + public OperationType getRestOperation() { + return myRestOperation; + } + + public void setTransactionLogMessages(TransactionLogMessages theTransactionLogMessages) { + myTransactionLogMessages = theTransactionLogMessages; + } + + public void setRestOperation(OperationType theRestOperation) { + myRestOperation = theRestOperation; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/BaseEmpiProvider.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/BaseEmpiProvider.java new file mode 100644 index 00000000000..e61d3e97abf --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/BaseEmpiProvider.java @@ -0,0 +1,171 @@ +package ca.uhn.fhir.empi.provider; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiConstants; +import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.model.EmpiTransactionContext; +import ca.uhn.fhir.empi.util.EmpiUtil; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.TransactionLogMessages; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; +import ca.uhn.fhir.validation.IResourceLoader; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; + +public abstract class BaseEmpiProvider { + + private final FhirContext myFhirContext; + private final IResourceLoader myResourceLoader; + + public BaseEmpiProvider(FhirContext theFhirContext, IResourceLoader theResourceLoader) { + myFhirContext = theFhirContext; + myResourceLoader = theResourceLoader; + } + + protected IAnyResource getPersonFromIdOrThrowException(String theParamName, String theId) { + IdDt personId = getPersonIdDtOrThrowException(theParamName, theId); + return loadResource(personId); + } + + private IdDt getPersonIdDtOrThrowException(String theParamName, String theId) { + IdDt personId = new IdDt(theId); + if (!"Person".equals(personId.getResourceType()) || + personId.getIdPart() == null) { + throw new InvalidRequestException(theParamName + " must have form Person/ where is the id of the person"); + } + return personId; + } + + protected IAnyResource getTargetFromIdOrThrowException(String theParamName, String theId) { + IIdType targetId = getTargetIdDtOrThrowException(theParamName, theId); + return loadResource(targetId); + } + + protected IIdType getTargetIdDtOrThrowException(String theParamName, String theId) { + IdDt targetId = new IdDt(theId); + String resourceType = targetId.getResourceType(); + if (!EmpiUtil.supportedTargetType(resourceType) || + targetId.getIdPart() == null) { + throw new InvalidRequestException(theParamName + " must have form Patient/ or Practitioner/ where is the id of the resource"); + } + return targetId; + } + + protected IAnyResource loadResource(IIdType theResourceId) { + Class resourceClass = myFhirContext.getResourceDefinition(theResourceId.getResourceType()).getImplementingClass(); + return (IAnyResource) myResourceLoader.load(resourceClass, theResourceId); + } + + protected void validateMergeParameters(IPrimitiveType thePersonIdToDelete, IPrimitiveType thePersonIdToKeep) { + validateNotNull(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE, thePersonIdToDelete); + validateNotNull(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP, thePersonIdToKeep); + if (thePersonIdToDelete.getValue().equals(thePersonIdToKeep.getValue())) { + throw new InvalidRequestException("personIdToDelete must be different from personToKeep"); + } + } + + protected void validateMergeResources(IAnyResource thePersonToDelete, IAnyResource thePersonToKeep) { + validateIsEmpiManaged(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE, thePersonToDelete); + validateIsEmpiManaged(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP, thePersonToKeep); + } + + private void validateIsEmpiManaged(String theName, IAnyResource thePerson) { + if (!"Person".equals(myFhirContext.getResourceType(thePerson))) { + throw new InvalidRequestException("Only Person resources can be merged. The " + theName + " points to a " + myFhirContext.getResourceType(thePerson)); + } + if (!EmpiUtil.isEmpiManaged(thePerson)) { + throw new InvalidRequestException("Only EMPI managed resources can be merged. Empi managed resource have the " + EmpiConstants.CODE_HAPI_EMPI_MANAGED + " tag."); + } + } + + private void validateNotNull(String theName, IPrimitiveType theString) { + if (theString == null || theString.getValue() == null) { + throw new InvalidRequestException(theName + " cannot be null"); + } + } + + protected void validateUpdateLinkParameters(IPrimitiveType thePersonId, IPrimitiveType theTargetId, IPrimitiveType theMatchResult) { + validateNotNull(ProviderConstants.EMPI_UPDATE_LINK_PERSON_ID, thePersonId); + validateNotNull(ProviderConstants.EMPI_UPDATE_LINK_TARGET_ID, theTargetId); + validateNotNull(ProviderConstants.EMPI_UPDATE_LINK_MATCH_RESULT, theMatchResult); + EmpiMatchResultEnum matchResult = EmpiMatchResultEnum.valueOf(theMatchResult.getValue()); + switch (matchResult) { + case NO_MATCH: + case MATCH: + break; + default: + throw new InvalidRequestException(ProviderConstants.EMPI_UPDATE_LINK + " illegal " + ProviderConstants.EMPI_UPDATE_LINK_MATCH_RESULT + + " value '" + matchResult + "'. Must be " + EmpiMatchResultEnum.NO_MATCH + " or " + EmpiMatchResultEnum.MATCH); + } + } + + protected EmpiTransactionContext createEmpiContext(RequestDetails theRequestDetails) { + TransactionLogMessages transactionLogMessages = TransactionLogMessages.createFromTransactionGuid(theRequestDetails.getTransactionGuid()); + return new EmpiTransactionContext(transactionLogMessages, EmpiTransactionContext.OperationType.MERGE_PERSONS); + } + + protected EmpiMatchResultEnum extractMatchResultOrNull(IPrimitiveType theMatchResult) { + String matchResult = extractStringNull(theMatchResult); + if (matchResult == null) { + return null; + } + return EmpiMatchResultEnum.valueOf(matchResult); + } + + protected EmpiLinkSourceEnum extractLinkSourceOrNull(IPrimitiveType theLinkSource) { + String linkSource = extractStringNull(theLinkSource); + if (linkSource == null) { + return null; + } + return EmpiLinkSourceEnum.valueOf(linkSource); + } + + private String extractStringNull(IPrimitiveType theString) { + if (theString == null) { + return null; + } + return theString.getValue(); + } + + protected IIdType extractPersonIdDtOrNull(String theName, IPrimitiveType thePersonId) { + String personId = extractStringNull(thePersonId); + if (personId == null) { + return null; + } + return getPersonIdDtOrThrowException(theName, personId); + } + + protected IIdType extractTargetIdDtOrNull(String theName, IPrimitiveType theTargetId) { + String targetId = extractStringNull(theTargetId); + if (targetId == null) { + return null; + } + return getTargetIdDtOrThrowException(theName, targetId); + } + +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java new file mode 100644 index 00000000000..cdeba5d9430 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderDstu3.java @@ -0,0 +1,133 @@ +package ca.uhn.fhir.empi.provider; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc; +import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc; +import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc; +import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.validation.IResourceLoader; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.InstantType; +import org.hl7.fhir.dstu3.model.Parameters; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Person; +import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IIdType; + +import java.util.Collection; +import java.util.UUID; + +public class EmpiProviderDstu3 extends BaseEmpiProvider { + private final IEmpiMatchFinderSvc myEmpiMatchFinderSvc; + private final IEmpiPersonMergerSvc myPersonMergerSvc; + private final IEmpiLinkUpdaterSvc myEmpiLinkUpdaterSvc; + private final IEmpiLinkQuerySvc myEmpiLinkQuerySvc; + + /** + * Constructor + * + * Note that this is not a spring bean. Any necessary injections should + * happen in the constructor + */ + public EmpiProviderDstu3(FhirContext theFhirContext, IEmpiMatchFinderSvc theEmpiMatchFinderSvc, IEmpiPersonMergerSvc thePersonMergerSvc, IEmpiLinkUpdaterSvc theEmpiLinkUpdaterSvc, IEmpiLinkQuerySvc theEmpiLinkQuerySvc, IResourceLoader theResourceLoader) { + super(theFhirContext, theResourceLoader); + myEmpiMatchFinderSvc = theEmpiMatchFinderSvc; + myPersonMergerSvc = thePersonMergerSvc; + myEmpiLinkUpdaterSvc = theEmpiLinkUpdaterSvc; + myEmpiLinkQuerySvc = theEmpiLinkQuerySvc; + } + + @Operation(name = ProviderConstants.EMPI_MATCH, type = Patient.class) + public Bundle match(@OperationParam(name=ProviderConstants.EMPI_MATCH_RESOURCE, min = 1, max = 1) Patient thePatient) { + if (thePatient == null) { + throw new InvalidRequestException("resource may not be null"); + } + Collection matches = myEmpiMatchFinderSvc.findMatches("Patient", thePatient); + + Bundle retVal = new Bundle(); + retVal.setType(Bundle.BundleType.SEARCHSET); + retVal.setId(UUID.randomUUID().toString()); + retVal.getMeta().setLastUpdatedElement(InstantType.now()); + + for (IAnyResource next : matches) { + retVal.addEntry().setResource((Resource) next); + } + + return retVal; + } + + @Operation(name = ProviderConstants.EMPI_MERGE_PERSONS, type = Person.class) + public Person mergePerson(@OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE, min = 1, max = 1) StringType thePersonIdToDelete, + @OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP, min = 1, max = 1) StringType thePersonIdToKeep, + RequestDetails theRequestDetails) { + validateMergeParameters(thePersonIdToDelete, thePersonIdToKeep); + IAnyResource personToDelete = getPersonFromIdOrThrowException(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE, thePersonIdToDelete.getValue()); + IAnyResource personToKeep = getPersonFromIdOrThrowException(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP, thePersonIdToKeep.getValue()); + validateMergeResources(personToDelete, personToKeep); + + return (Person) myPersonMergerSvc.mergePersons(personToDelete, personToKeep, createEmpiContext(theRequestDetails)); + } + + @Operation(name = ProviderConstants.EMPI_UPDATE_LINK, type = Person.class) + public Person updateLink(@OperationParam(name=ProviderConstants.EMPI_UPDATE_LINK_PERSON_ID, min = 1, max = 1) StringType thePersonId, + @OperationParam(name=ProviderConstants.EMPI_UPDATE_LINK_TARGET_ID, min = 1, max = 1) StringType theTargetId, + @OperationParam(name=ProviderConstants.EMPI_UPDATE_LINK_MATCH_RESULT, min = 1, max = 1) StringType theMatchResult, + ServletRequestDetails theRequestDetails) { + + validateUpdateLinkParameters(thePersonId, theTargetId, theMatchResult); + EmpiMatchResultEnum matchResult = extractMatchResultOrNull(theMatchResult); + IAnyResource person = getPersonFromIdOrThrowException(ProviderConstants.EMPI_UPDATE_LINK_PERSON_ID, thePersonId.getValue()); + IAnyResource target = getTargetFromIdOrThrowException(ProviderConstants.EMPI_UPDATE_LINK_TARGET_ID, theTargetId.getValue()); + + return (Person) myEmpiLinkUpdaterSvc.updateLink(person, target, matchResult, createEmpiContext(theRequestDetails)); + } + + @Operation(name = ProviderConstants.EMPI_QUERY_LINKS) + public Parameters queryLinks(@OperationParam(name=ProviderConstants.EMPI_QUERY_LINKS_PERSON_ID, min = 0, max = 1) StringType thePersonId, + @OperationParam(name=ProviderConstants.EMPI_QUERY_LINKS_TARGET_ID, min = 0, max = 1) StringType theTargetId, + @OperationParam(name=ProviderConstants.EMPI_QUERY_LINKS_MATCH_RESULT, min = 0, max = 1) StringType theMatchResult, + @OperationParam(name=ProviderConstants.EMPI_QUERY_LINKS_MATCH_RESULT, min = 0, max = 1) StringType theLinkSource, + ServletRequestDetails theRequestDetails) { + IIdType personId = extractPersonIdDtOrNull(ProviderConstants.EMPI_QUERY_LINKS_PERSON_ID, thePersonId); + IIdType targetId = extractTargetIdDtOrNull(ProviderConstants.EMPI_QUERY_LINKS_TARGET_ID, theTargetId); + EmpiMatchResultEnum matchResult = extractMatchResultOrNull(theMatchResult); + EmpiLinkSourceEnum linkSource = extractLinkSourceOrNull(theLinkSource); + + return (Parameters) myEmpiLinkQuerySvc.queryLinks(personId, targetId, matchResult, linkSource, createEmpiContext(theRequestDetails)); + } + + @Operation(name = ProviderConstants.EMPI_DUPLICATE_PERSONS) + public Parameters getDuplicatePersons(ServletRequestDetails theRequestDetails) { + return (Parameters) myEmpiLinkQuerySvc.getPossibleDuplicates(createEmpiContext(theRequestDetails)); + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderLoader.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderLoader.java new file mode 100644 index 00000000000..81a4f90ee46 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderLoader.java @@ -0,0 +1,64 @@ +package ca.uhn.fhir.empi.provider; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc; +import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc; +import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc; +import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc; +import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; +import ca.uhn.fhir.validation.IResourceLoader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EmpiProviderLoader { + @Autowired + private FhirContext myFhirContext; + @Autowired + private ResourceProviderFactory myResourceProviderFactory; + @Autowired + private IEmpiMatchFinderSvc myEmpiMatchFinderSvc; + @Autowired + private IEmpiPersonMergerSvc myPersonMergerSvc; + @Autowired + private IEmpiLinkUpdaterSvc myEmpiLinkUpdaterSvc; + @Autowired + private IEmpiLinkQuerySvc myEmpiLinkQuerySvc; + @Autowired + private IResourceLoader myResourceLoader; + + public void loadProvider() { + switch (myFhirContext.getVersion().getVersion()) { + case DSTU3: + myResourceProviderFactory.addSupplier(() -> new EmpiProviderDstu3(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader)); + break; + case R4: + myResourceProviderFactory.addSupplier(() -> new EmpiProviderR4(myFhirContext, myEmpiMatchFinderSvc, myPersonMergerSvc, myEmpiLinkUpdaterSvc, myEmpiLinkQuerySvc, myResourceLoader)); + break; + default: + throw new ConfigurationException("EMPI not supported for FHIR version " + myFhirContext.getVersion().getVersion()); + } + } +} + diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java new file mode 100644 index 00000000000..73c12737379 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/provider/EmpiProviderR4.java @@ -0,0 +1,134 @@ +package ca.uhn.fhir.empi.provider; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc; +import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc; +import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc; +import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.validation.IResourceLoader; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.InstantType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.StringType; + +import java.util.Collection; +import java.util.UUID; + +public class EmpiProviderR4 extends BaseEmpiProvider { + private final IEmpiMatchFinderSvc myEmpiMatchFinderSvc; + private final IEmpiPersonMergerSvc myPersonMergerSvc; + private final IEmpiLinkUpdaterSvc myEmpiLinkUpdaterSvc; + private final IEmpiLinkQuerySvc myEmpiLinkQuerySvc; + + /** + * Constructor + * + * Note that this is not a spring bean. Any necessary injections should + * happen in the constructor + */ + public EmpiProviderR4(FhirContext theFhirContext, IEmpiMatchFinderSvc theEmpiMatchFinderSvc, IEmpiPersonMergerSvc thePersonMergerSvc, IEmpiLinkUpdaterSvc theEmpiLinkUpdaterSvc, IEmpiLinkQuerySvc theEmpiLinkQuerySvc, IResourceLoader theResourceLoader) { + super(theFhirContext, theResourceLoader); + myEmpiMatchFinderSvc = theEmpiMatchFinderSvc; + myPersonMergerSvc = thePersonMergerSvc; + myEmpiLinkUpdaterSvc = theEmpiLinkUpdaterSvc; + myEmpiLinkQuerySvc = theEmpiLinkQuerySvc; + } + + @Operation(name = ProviderConstants.EMPI_MATCH, type = Patient.class) + public Bundle match(@OperationParam(name=ProviderConstants.EMPI_MATCH_RESOURCE, min = 1, max = 1) Patient thePatient) { + if (thePatient == null) { + throw new InvalidRequestException("resource may not be null"); + } + + Collection matches = myEmpiMatchFinderSvc.findMatches("Patient", thePatient); + + Bundle retVal = new Bundle(); + retVal.setType(Bundle.BundleType.SEARCHSET); + retVal.setId(UUID.randomUUID().toString()); + retVal.getMeta().setLastUpdatedElement(InstantType.now()); + + for (IAnyResource next : matches) { + retVal.addEntry().setResource((Resource) next); + } + + return retVal; + } + + @Operation(name = ProviderConstants.EMPI_MERGE_PERSONS, type = Person.class) + public Person mergePersons(@OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE, min = 1, max = 1) StringType thePersonIdToDelete, + @OperationParam(name=ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP, min = 1, max = 1) StringType thePersonIdToKeep, + RequestDetails theRequestDetails) { + validateMergeParameters(thePersonIdToDelete, thePersonIdToKeep); + IAnyResource personToDelete = getPersonFromIdOrThrowException(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE, thePersonIdToDelete.getValue()); + IAnyResource personToKeep = getPersonFromIdOrThrowException(ProviderConstants.EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP, thePersonIdToKeep.getValue()); + validateMergeResources(personToDelete, personToKeep); + + return (Person) myPersonMergerSvc.mergePersons(personToDelete, personToKeep, createEmpiContext(theRequestDetails)); + } + + @Operation(name = ProviderConstants.EMPI_UPDATE_LINK, type = Person.class) + public Person updateLink(@OperationParam(name=ProviderConstants.EMPI_UPDATE_LINK_PERSON_ID, min = 1, max = 1) StringType thePersonId, + @OperationParam(name=ProviderConstants.EMPI_UPDATE_LINK_TARGET_ID, min = 1, max = 1) StringType theTargetId, + @OperationParam(name=ProviderConstants.EMPI_UPDATE_LINK_MATCH_RESULT, min = 1, max = 1) StringType theMatchResult, + ServletRequestDetails theRequestDetails) { + + validateUpdateLinkParameters(thePersonId, theTargetId, theMatchResult); + EmpiMatchResultEnum matchResult = extractMatchResultOrNull(theMatchResult); + IAnyResource person = getPersonFromIdOrThrowException(ProviderConstants.EMPI_UPDATE_LINK_PERSON_ID, thePersonId.getValue()); + IAnyResource target = getTargetFromIdOrThrowException(ProviderConstants.EMPI_UPDATE_LINK_TARGET_ID, theTargetId.getValue()); + + return (Person) myEmpiLinkUpdaterSvc.updateLink(person, target, matchResult, createEmpiContext(theRequestDetails)); + } + + @Operation(name = ProviderConstants.EMPI_QUERY_LINKS, idempotent = true) + public Parameters queryLinks(@OperationParam(name=ProviderConstants.EMPI_QUERY_LINKS_PERSON_ID, min = 0, max = 1) StringType thePersonId, + @OperationParam(name=ProviderConstants.EMPI_QUERY_LINKS_TARGET_ID, min = 0, max = 1) StringType theTargetId, + @OperationParam(name=ProviderConstants.EMPI_QUERY_LINKS_MATCH_RESULT, min = 0, max = 1) StringType theMatchResult, + @OperationParam(name=ProviderConstants.EMPI_QUERY_LINKS_LINK_SOURCE, min = 0, max = 1) StringType theLinkSource, + ServletRequestDetails theRequestDetails) { + IIdType personId = extractPersonIdDtOrNull(ProviderConstants.EMPI_QUERY_LINKS_PERSON_ID, thePersonId); + IIdType targetId = extractTargetIdDtOrNull(ProviderConstants.EMPI_QUERY_LINKS_TARGET_ID, theTargetId); + EmpiMatchResultEnum matchResult = extractMatchResultOrNull(theMatchResult); + EmpiLinkSourceEnum linkSource = extractLinkSourceOrNull(theLinkSource); + + return (Parameters) myEmpiLinkQuerySvc.queryLinks(personId, targetId, matchResult, linkSource, createEmpiContext(theRequestDetails)); + } + + @Operation(name = ProviderConstants.EMPI_DUPLICATE_PERSONS, idempotent = true) + public Parameters getDuplicatePersons(ServletRequestDetails theRequestDetails) { + return (Parameters) myEmpiLinkQuerySvc.getPossibleDuplicates(createEmpiContext(theRequestDetails)); + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiRuleValidator.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiRuleValidator.java new file mode 100644 index 00000000000..5d343bbafbb --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiRuleValidator.java @@ -0,0 +1,44 @@ +package ca.uhn.fhir.empi.rules.config; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.ConfigurationException; +import ca.uhn.fhir.empi.rules.json.EmpiRulesJson; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.net.URISyntaxException; + +@Service +public class EmpiRuleValidator { + + public void validate(EmpiRulesJson theEmpiRulesJson) { + validateSystemIsUri(theEmpiRulesJson); + } + + private void validateSystemIsUri(EmpiRulesJson theEmpiRulesJson) { + try { + new URI(theEmpiRulesJson.getEnterpriseEIDSystem()); + } catch (URISyntaxException e) { + throw new ConfigurationException("Enterprise Identifier System (eidSystem) must be a valid URI"); + } + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiSettings.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiSettings.java new file mode 100644 index 00000000000..b08a66d1512 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiSettings.java @@ -0,0 +1,103 @@ +package ca.uhn.fhir.empi.rules.config; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.rules.json.EmpiRulesJson; +import ca.uhn.fhir.util.JsonUtil; + +import java.io.IOException; + +public class EmpiSettings implements IEmpiSettings { + private boolean myEnabled; + private int myConcurrentConsumers = EMPI_DEFAULT_CONCURRENT_CONSUMERS; + private String myScriptText; + private EmpiRulesJson myEmpiRules; + private boolean myPreventEidUpdates; + + /** + * If disabled, the underlying EMPI system will operate under the following assumptions: + * + * 1. Patients/Practitioners may have more than 1 EID of the same system simultaneously. + * 2. During linking, incoming patient EIDs will be merged with existing Person EIDs. + * + */ + private boolean myPreventMultipleEids; + + @Override + public boolean isEnabled() { + return myEnabled; + } + + public EmpiSettings setEnabled(boolean theEnabled) { + myEnabled = theEnabled; + return this; + } + + @Override + public int getConcurrentConsumers() { + return myConcurrentConsumers; + } + + public EmpiSettings setConcurrentConsumers(int theConcurrentConsumers) { + myConcurrentConsumers = theConcurrentConsumers; + return this; + } + + public String getScriptText() { + return myScriptText; + } + + public EmpiSettings setScriptText(String theScriptText) throws IOException { + myScriptText = theScriptText; + myEmpiRules = JsonUtil.deserialize(theScriptText, EmpiRulesJson.class); + return this; + } + + @Override + public EmpiRulesJson getEmpiRules() { + return myEmpiRules; + } + + @Override + public boolean isPreventEidUpdates() { + return myPreventEidUpdates; + } + + public EmpiSettings setPreventEidUpdates(boolean thePreventEidUpdates) { + myPreventEidUpdates = thePreventEidUpdates; + return this; + } + + public EmpiSettings setEmpiRules(EmpiRulesJson theEmpiRules) { + myEmpiRules = theEmpiRules; + return this; + } + + public boolean isPreventMultipleEids() { + return myPreventMultipleEids; + } + + public EmpiSettings setPreventMultipleEids(boolean thePreventMultipleEids) { + myPreventMultipleEids = thePreventMultipleEids; + return this; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/DistanceMetricEnum.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/DistanceMetricEnum.java new file mode 100644 index 00000000000..2830bcfd121 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/DistanceMetricEnum.java @@ -0,0 +1,71 @@ +package ca.uhn.fhir.empi.rules.json; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.rules.similarity.EmpiPersonNameMatchModeEnum; +import ca.uhn.fhir.empi.rules.similarity.HapiStringSimilarity; +import ca.uhn.fhir.empi.rules.similarity.IEmpiFieldSimilarity; +import ca.uhn.fhir.empi.rules.similarity.NameSimilarity; +import info.debatty.java.stringsimilarity.Cosine; +import info.debatty.java.stringsimilarity.Jaccard; +import info.debatty.java.stringsimilarity.JaroWinkler; +import info.debatty.java.stringsimilarity.NormalizedLevenshtein; +import info.debatty.java.stringsimilarity.SorensenDice; +import org.hl7.fhir.instance.model.api.IBase; + +/** + * Enum for holding all the known distance metrics that we support in HAPI for + * calculating differences between strings (https://en.wikipedia.org/wiki/String_metric) + */ +public enum DistanceMetricEnum implements IEmpiFieldSimilarity { + JARO_WINKLER("Jaro Winkler", new HapiStringSimilarity(new JaroWinkler())), + COSINE("Cosine", new HapiStringSimilarity(new Cosine())), + JACCARD("Jaccard", new HapiStringSimilarity(new Jaccard())), + NORMALIZED_LEVENSCHTEIN("Normalized Levenschtein", new HapiStringSimilarity(new NormalizedLevenshtein())), + SORENSEN_DICE("Sorensen Dice", new HapiStringSimilarity(new SorensenDice())), + STANDARD_NAME_ANY_ORDER("Standard name Any Order", new NameSimilarity(EmpiPersonNameMatchModeEnum.STANDARD_ANY_ORDER)), + EXACT_NAME_ANY_ORDER("Exact name Any Order", new NameSimilarity(EmpiPersonNameMatchModeEnum.EXACT_ANY_ORDER)), + STANDARD_NAME_FIRST_AND_LAST("Standard name First and Last", new NameSimilarity(EmpiPersonNameMatchModeEnum.STANDARD_FIRST_AND_LAST)), + EXACT_NAME_FIRST_AND_LAST("Exact name First and Last", new NameSimilarity(EmpiPersonNameMatchModeEnum.EXACT_FIRST_AND_LAST)); + + private final String myCode; + private final IEmpiFieldSimilarity myEmpiFieldSimilarity; + + DistanceMetricEnum(String theCode, IEmpiFieldSimilarity theEmpiFieldSimilarity) { + myCode = theCode; + myEmpiFieldSimilarity = theEmpiFieldSimilarity; + } + + public String getCode() { + return myCode; + } + + public IEmpiFieldSimilarity getEmpiFieldSimilarity() { + return myEmpiFieldSimilarity; + } + + @Override + public double similarity(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase) { + return myEmpiFieldSimilarity.similarity(theFhirContext ,theLeftBase, theRightBase); + } + +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiFieldMatchJson.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiFieldMatchJson.java new file mode 100644 index 00000000000..20f2b007041 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiFieldMatchJson.java @@ -0,0 +1,91 @@ +package ca.uhn.fhir.empi.rules.json; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.model.api.IModelJson; +import com.fasterxml.jackson.annotation.JsonProperty; + +import javax.annotation.Nonnull; + +/** + * Contains all business data for determining if a match exists on a particular field, given: + * + * 1. A {@link DistanceMetricEnum} which determines the actual similarity values. + * 2. A given resource type (e.g. Patient) + * 3. A given FHIRPath expression for finding the particular primitive to be used for comparison. (e.g. name.given) + */ +public class EmpiFieldMatchJson implements IModelJson { + @JsonProperty("name") + String myName; + @JsonProperty("resourceType") + String myResourceType; + @JsonProperty("resourcePath") + String myResourcePath; + @JsonProperty("metric") + DistanceMetricEnum myMetric; + @JsonProperty("matchThreshold") + double myMatchThreshold; + + public DistanceMetricEnum getMetric() { + return myMetric; + } + + public EmpiFieldMatchJson setMetric(DistanceMetricEnum theMetric) { + myMetric = theMetric; + return this; + } + + public String getResourceType() { + return myResourceType; + } + + public EmpiFieldMatchJson setResourceType(String theResourceType) { + myResourceType = theResourceType; + return this; + } + + public String getResourcePath() { + return myResourcePath; + } + + public EmpiFieldMatchJson setResourcePath(String theResourcePath) { + myResourcePath = theResourcePath; + return this; + } + + public double getMatchThreshold() { + return myMatchThreshold; + } + + public EmpiFieldMatchJson setMatchThreshold(double theMatchThreshold) { + myMatchThreshold = theMatchThreshold; + return this; + } + + public String getName() { + return myName; + } + + public EmpiFieldMatchJson setName(@Nonnull String theName) { + myName = theName; + return this; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiFilterSearchParamJson.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiFilterSearchParamJson.java new file mode 100644 index 00000000000..2313cf5903d --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiFilterSearchParamJson.java @@ -0,0 +1,81 @@ +package ca.uhn.fhir.empi.rules.json; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.model.api.IModelJson; +import ca.uhn.fhir.rest.param.TokenParamModifier; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * This SearchParamJson, unlike EmpiREsourceSearchParamJson, is responsible for doing inclusions during empi + * candidate searching. e.g. When doing candidate matching, only consider candidates that match all EmpiFilterSearchParams. + */ +public class EmpiFilterSearchParamJson implements IModelJson { + @JsonProperty("resourceType") + String myResourceType; + @JsonProperty("searchParam") + String mySearchParam; + @JsonProperty("qualifier") + TokenParamModifier myTokenParamModifier; + @JsonProperty("fixedValue") + String myFixedValue; + + public String getResourceType() { + return myResourceType; + } + + public EmpiFilterSearchParamJson setResourceType(String theResourceType) { + myResourceType = theResourceType; + return this; + } + + public String getSearchParam() { + return mySearchParam; + } + + public EmpiFilterSearchParamJson setSearchParam(String theSearchParam) { + mySearchParam = theSearchParam; + return this; + } + + + public TokenParamModifier getTokenParamModifier() { + return myTokenParamModifier; + } + + public EmpiFilterSearchParamJson setTokenParamModifier(TokenParamModifier theTokenParamModifier) { + myTokenParamModifier = theTokenParamModifier; + return this; + } + + public String getFixedValue() { + return myFixedValue; + } + + public EmpiFilterSearchParamJson setFixedValue(String theFixedValue) { + myFixedValue = theFixedValue; + return this; + } + + public String getTokenParamModifierAsString() { + return myTokenParamModifier == null ? "" : myTokenParamModifier.getValue(); + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiResourceSearchParamJson.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiResourceSearchParamJson.java new file mode 100644 index 00000000000..082b7c7b721 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiResourceSearchParamJson.java @@ -0,0 +1,52 @@ +package ca.uhn.fhir.empi.rules.json; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.model.api.IModelJson; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * + */ +public class EmpiResourceSearchParamJson implements IModelJson { + @JsonProperty("resourceType") + String myResourceType; + @JsonProperty("searchParam") + String mySearchParam; + + public String getResourceType() { + return myResourceType; + } + + public EmpiResourceSearchParamJson setResourceType(String theResourceType) { + myResourceType = theResourceType; + return this; + } + + public String getSearchParam() { + return mySearchParam; + } + + public EmpiResourceSearchParamJson setSearchParam(String theSearchParam) { + mySearchParam = theSearchParam; + return this; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJson.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJson.java new file mode 100644 index 00000000000..8869bb77589 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJson.java @@ -0,0 +1,149 @@ +package ca.uhn.fhir.empi.rules.json; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.model.api.IModelJson; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.util.StdConverter; +import com.google.common.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@JsonDeserialize(converter = EmpiRulesJson.EmpiRulesJsonConverter.class) +public class EmpiRulesJson implements IModelJson { + @JsonProperty("candidateSearchParams") + List myResourceSearchParams = new ArrayList<>(); + @JsonProperty("candidateFilterSearchParams") + List myFilterSearchParams = new ArrayList<>(); + @JsonProperty("matchFields") + List myMatchFieldJsonList = new ArrayList<>(); + @JsonProperty("matchResultMap") + Map myMatchResultMap = new HashMap<>(); + @JsonProperty(value = "eidSystem") + String myEnterpriseEIDSystem; + + transient VectorMatchResultMap myVectorMatchResultMap; + + public void addMatchField(EmpiFieldMatchJson theMatchRuleName) { + myMatchFieldJsonList.add(theMatchRuleName); + } + + public void addResourceSearchParam(EmpiResourceSearchParamJson theSearchParam) { + myResourceSearchParams.add(theSearchParam); + } + + public void addFilterSearchParam(EmpiFilterSearchParamJson theSearchParam) { + myFilterSearchParams.add(theSearchParam); + } + + int size() { + return myMatchFieldJsonList.size(); + } + + EmpiFieldMatchJson get(int theIndex) { + return myMatchFieldJsonList.get(theIndex); + } + + EmpiMatchResultEnum getMatchResult(String theFieldMatchNames) { + return myMatchResultMap.get(theFieldMatchNames); + } + + public EmpiMatchResultEnum getMatchResult(Long theMatchVector) { + EmpiMatchResultEnum result = myVectorMatchResultMap.get(theMatchVector); + return (result == null) ? EmpiMatchResultEnum.NO_MATCH : result; + } + + public void putMatchResult(String theFieldMatchNames, EmpiMatchResultEnum theMatchResult) { + myMatchResultMap.put(theFieldMatchNames, theMatchResult); + initialize(); + } + + Map getMatchResultMap() { + return Collections.unmodifiableMap(myMatchResultMap); + } + + /** + * Must call initialize() before calling getMatchResult(Long) + */ + public void initialize() { + myVectorMatchResultMap = new VectorMatchResultMap(this); + } + + public List getMatchFields() { + return Collections.unmodifiableList(myMatchFieldJsonList); + } + + public List getResourceSearchParams() { + return Collections.unmodifiableList(myResourceSearchParams); + } + + public List getFilterSearchParams() { + return Collections.unmodifiableList(myFilterSearchParams); + } + + public String getEnterpriseEIDSystem() { + return myEnterpriseEIDSystem; + } + + public void setEnterpriseEIDSystem(String theEnterpriseEIDSystem) { + myEnterpriseEIDSystem = theEnterpriseEIDSystem; + } + + /** + * Ensure the vector map is initialized after we deserialize + */ + static class EmpiRulesJsonConverter extends StdConverter { + + /** + * This empty constructor is required by Jackson + */ + public EmpiRulesJsonConverter() { + } + + @Override + public EmpiRulesJson convert(EmpiRulesJson theEmpiRulesJson) { + theEmpiRulesJson.initialize(); + return theEmpiRulesJson; + } + } + + public String getSummary() { + return myResourceSearchParams.size() + " Candidate Search Params, " + + myFilterSearchParams.size() + " Filter Search Params, " + + myMatchFieldJsonList.size() + " Match Fields, " + + myMatchResultMap.size() + " Match Result Entries"; + } + + public String getFieldMatchNamesForVector(long theVector) { + return myVectorMatchResultMap.getFieldMatchNames(theVector); + } + + @VisibleForTesting + VectorMatchResultMap getVectorMatchResultMapForUnitTest() { + return myVectorMatchResultMap; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/VectorMatchResultMap.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/VectorMatchResultMap.java new file mode 100644 index 00000000000..4aeb5094577 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/VectorMatchResultMap.java @@ -0,0 +1,85 @@ +package ca.uhn.fhir.empi.rules.json; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiMatchResultEnum; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; + +public class VectorMatchResultMap { + private final EmpiRulesJson myEmpiRulesJson; + private Map myVectorToMatchResultMap = new HashMap<>(); + private Map myVectorToFieldMatchNamesMap = new HashMap<>(); + + VectorMatchResultMap(EmpiRulesJson theEmpiRulesJson) { + myEmpiRulesJson = theEmpiRulesJson; + //no reason to hold the entire empirulesjson here + initMap(); + } + + private void initMap() { + for (Map.Entry entry : myEmpiRulesJson.getMatchResultMap().entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + public EmpiMatchResultEnum get(Long theMatchVector) { + return myVectorToMatchResultMap.get(theMatchVector); + } + + private void put(String theFieldMatchNames, EmpiMatchResultEnum theMatchResult) { + long vector = getVector(theFieldMatchNames); + myVectorToFieldMatchNamesMap.put(vector, theFieldMatchNames); + myVectorToMatchResultMap.put(vector, theMatchResult); + } + + public long getVector(String theFieldMatchNames) { + long retval = 0; + for (String fieldMatchName : splitFieldMatchNames(theFieldMatchNames)) { + int index = getFieldMatchIndex(fieldMatchName); + if (index == -1) { + throw new IllegalArgumentException("There is no matchField with name " + fieldMatchName); + } + retval |= (1 << index); + } + return retval; + } + + @Nonnull + static String[] splitFieldMatchNames(String theFieldMatchNames) { + return theFieldMatchNames.split(",\\s*"); + } + + private int getFieldMatchIndex(final String theFieldMatchName) { + for (int i = 0; i < myEmpiRulesJson.size(); ++i) { + if (myEmpiRulesJson.get(i).getName().equals(theFieldMatchName)) { + return i; + } + } + return -1; + } + + public String getFieldMatchNames(long theVector) { + return myVectorToFieldMatchNamesMap.get(theVector); + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/EmpiPersonNameMatchModeEnum.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/EmpiPersonNameMatchModeEnum.java new file mode 100644 index 00000000000..fdbbf798ece --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/EmpiPersonNameMatchModeEnum.java @@ -0,0 +1,28 @@ +package ca.uhn.fhir.empi.rules.similarity; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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 enum EmpiPersonNameMatchModeEnum { + STANDARD_ANY_ORDER, + EXACT_ANY_ORDER, + STANDARD_FIRST_AND_LAST, + EXACT_FIRST_AND_LAST +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/HapiStringSimilarity.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/HapiStringSimilarity.java new file mode 100644 index 00000000000..7a04123e7fa --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/HapiStringSimilarity.java @@ -0,0 +1,46 @@ +package ca.uhn.fhir.empi.rules.similarity; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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 info.debatty.java.stringsimilarity.interfaces.NormalizedStringSimilarity; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IPrimitiveType; + +/** + * Similarity measure for two IBase fields whose similarity can be measured by their String representations. + */ +public class HapiStringSimilarity implements IEmpiFieldSimilarity { + private final NormalizedStringSimilarity myStringSimilarity; + + public HapiStringSimilarity(NormalizedStringSimilarity theStringSimilarity) { + myStringSimilarity = theStringSimilarity; + } + + public double similarity(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase) { + if (theLeftBase instanceof IPrimitiveType && theRightBase instanceof IPrimitiveType) { + IPrimitiveType leftString = (IPrimitiveType) theLeftBase; + IPrimitiveType rightString = (IPrimitiveType) theRightBase; + return myStringSimilarity.similarity(leftString.getValueAsString(), rightString.getValueAsString()); + } + return 0.0; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/IEmpiFieldSimilarity.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/IEmpiFieldSimilarity.java new file mode 100644 index 00000000000..e3ee1c23b10 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/IEmpiFieldSimilarity.java @@ -0,0 +1,31 @@ +package ca.uhn.fhir.empi.rules.similarity; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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 org.hl7.fhir.instance.model.api.IBase; + +/** + * Measure how similar two IBase (resource fields) are to one another. 1.0 means identical. 0.0 means completely different. + */ +public interface IEmpiFieldSimilarity { + double similarity(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase); +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/NameSimilarity.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/NameSimilarity.java new file mode 100644 index 00000000000..c4639c8e08e --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/NameSimilarity.java @@ -0,0 +1,76 @@ +package ca.uhn.fhir.empi.rules.similarity; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.util.NameUtil; +import ca.uhn.fhir.util.StringNormalizer; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.instance.model.api.IBase; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Similarity measure for two IBase name fields + */ +public class NameSimilarity implements IEmpiFieldSimilarity { + private final EmpiPersonNameMatchModeEnum myMatchMode; + + public NameSimilarity(EmpiPersonNameMatchModeEnum theMatchMode) { + myMatchMode = theMatchMode; + } + + @Override + public double similarity(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase) { + String leftFamilyName = NameUtil.extractFamilyName(theFhirContext, theLeftBase); + String rightFamilyName = NameUtil.extractFamilyName(theFhirContext, theRightBase); + if (StringUtils.isEmpty(leftFamilyName) || StringUtils.isEmpty(rightFamilyName)) { + return 0.0; + } + + boolean match = false; + boolean exact = + myMatchMode == EmpiPersonNameMatchModeEnum.EXACT_ANY_ORDER || + myMatchMode == EmpiPersonNameMatchModeEnum.STANDARD_FIRST_AND_LAST; + + List leftGivenNames = NameUtil.extractGivenNames(theFhirContext, theLeftBase); + List rightGivenNames = NameUtil.extractGivenNames(theFhirContext, theRightBase); + + if (!exact) { + leftFamilyName = StringNormalizer.normalizeStringForSearchIndexing(leftFamilyName); + rightFamilyName = StringNormalizer.normalizeStringForSearchIndexing(rightFamilyName); + leftGivenNames = leftGivenNames.stream().map(StringNormalizer::normalizeStringForSearchIndexing).collect(Collectors.toList()); + rightGivenNames = rightGivenNames.stream().map(StringNormalizer::normalizeStringForSearchIndexing).collect(Collectors.toList()); + } + + for (String leftGivenName : leftGivenNames) { + for (String rightGivenName : rightGivenNames) { + match |= leftGivenName.equals(rightGivenName) && leftFamilyName.equals(rightFamilyName); + if (myMatchMode == EmpiPersonNameMatchModeEnum.STANDARD_ANY_ORDER || myMatchMode == EmpiPersonNameMatchModeEnum.EXACT_ANY_ORDER) { + match |= leftGivenName.equals(rightFamilyName) && leftFamilyName.equals(rightGivenName); + } + } + } + + return match ? 1.0 : 0.0; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/ReferenceMatchSimilarity.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/ReferenceMatchSimilarity.java new file mode 100644 index 00000000000..eb65f7dc6f6 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/similarity/ReferenceMatchSimilarity.java @@ -0,0 +1,32 @@ +package ca.uhn.fhir.empi.rules.similarity; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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 org.hl7.fhir.instance.model.api.IBase; + +public class ReferenceMatchSimilarity implements IEmpiFieldSimilarity { + @Override + public double similarity(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase) { + System.out.println("wip!"); + return 1; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceComparatorSvc.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceComparatorSvc.java new file mode 100644 index 00000000000..3712d168223 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceComparatorSvc.java @@ -0,0 +1,120 @@ +package ca.uhn.fhir.empi.rules.svc; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.empi.rules.json.EmpiFieldMatchJson; +import ca.uhn.fhir.empi.rules.json.EmpiRulesJson; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; + +/** + * The EmpiResourceComparator is in charge of performing actual comparisons between left and right records. + * It does so by calling individual comparators, and returning a vector based on the combination of + * field comparators that matched. + */ + +@Service +public class EmpiResourceComparatorSvc { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + private final FhirContext myFhirContext; + private final IEmpiSettings myEmpiConfig; + private EmpiRulesJson myEmpiRulesJson; + private final List myFieldComparators = new ArrayList<>(); + + @Autowired + public EmpiResourceComparatorSvc(FhirContext theFhirContext, IEmpiSettings theEmpiConfig) { + myFhirContext = theFhirContext; + myEmpiConfig = theEmpiConfig; + } + + @PostConstruct + public void init() { + myEmpiRulesJson = myEmpiConfig.getEmpiRules(); + if (myEmpiRulesJson == null) { + throw new ConfigurationException("Failed to load EMPI Rules. If EMPI is enabled, then EMPI rules must be available in context."); + } + for (EmpiFieldMatchJson matchFieldJson : myEmpiRulesJson.getMatchFields()) { + myFieldComparators.add(new EmpiResourceFieldComparator(myFhirContext, matchFieldJson)); + } + + } + + /** + * Given two {@link IBaseResource}s, perform all comparisons on them to determine an {@link EmpiMatchResultEnum}, indicating + * to what level the two resources are considered to be matching. + * + * @param theLeftResource The first {@link IBaseResource}. + * @param theRightResource The second {@link IBaseResource} + * + * @return an {@link EmpiMatchResultEnum} indicating the result of the comparison. + */ + public EmpiMatchResultEnum getMatchResult(IBaseResource theLeftResource, IBaseResource theRightResource) { + return compare(theLeftResource, theRightResource); + } + + EmpiMatchResultEnum compare(IBaseResource theLeftResource, IBaseResource theRightResource) { + long matchVector = getMatchVector(theLeftResource, theRightResource); + EmpiMatchResultEnum matchResult = myEmpiRulesJson.getMatchResult(matchVector); + if (ourLog.isTraceEnabled() && matchResult == EmpiMatchResultEnum.MATCH || matchResult == EmpiMatchResultEnum.POSSIBLE_MATCH) { + ourLog.trace("{} {} with field matchers {}", matchResult, theRightResource.getIdElement().toUnqualifiedVersionless(), myEmpiRulesJson.getFieldMatchNamesForVector(matchVector)); + } + return matchResult; + } + + /** + * This function generates a `match vector`, which is a long representation of a binary string + * generated by the results of each of the given comparator matches. For example. + * start with a binary representation of the value 0 for long: 0000 + * first_name matches, so the value `1` is bitwise-ORed to the current value (0) in right-most position. + * `0001` + * + * Next, we look at the second field comparator, and see if it matches. If it does, we left-shift 1 by the index + * of the comparator, in this case also 1. + * `0010` + * + * Then, we bitwise-or it with the current retval: + * 0001|0010 = 0011 + * The binary string is now `0011`, which when you return it as a long becomes `3`. + */ + private long getMatchVector(IBaseResource theLeftResource, IBaseResource theRightResource) { + long retval = 0; + for (int i = 0; i < myFieldComparators.size(); ++i) { + //any that are not for the resourceType in question. + EmpiResourceFieldComparator fieldComparator = myFieldComparators.get(i); + if (fieldComparator.match(theLeftResource, theRightResource)) { + retval |= (1 << i); + } + } + return retval; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldComparator.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldComparator.java new file mode 100644 index 00000000000..f3d39a55c0b --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldComparator.java @@ -0,0 +1,96 @@ +package ca.uhn.fhir.empi.rules.svc; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.rules.json.EmpiFieldMatchJson; +import ca.uhn.fhir.util.FhirTerser; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.List; + +import static ca.uhn.fhir.empi.api.EmpiConstants.ALL_RESOURCE_SEARCH_PARAM_TYPE; + +/** + * This class is responsible for performing matching between raw-typed values of a left record and a right record. + */ +public class EmpiResourceFieldComparator { + private final FhirContext myFhirContext; + private final EmpiFieldMatchJson myEmpiFieldMatchJson; + private final String myResourceType; + private final String myResourcePath; + + public EmpiResourceFieldComparator(FhirContext theFhirContext, EmpiFieldMatchJson theEmpiFieldMatchJson) { + myFhirContext = theFhirContext; + myEmpiFieldMatchJson = theEmpiFieldMatchJson; + myResourceType = theEmpiFieldMatchJson.getResourceType(); + myResourcePath = theEmpiFieldMatchJson.getResourcePath(); + } + + /** + * Compares two {@link IBaseResource}s and determines if they match, using the algorithm defined in this object's EmpiFieldMatchJson. + * + * In this implementation, it determines whether a given field matches between two resources. Internally this is evaluated using FhirPath. If any of the elements of theLeftResource + * match any of the elements of theRightResource, will return true. Otherwise, false. + * + * @param theLeftResource the first {@link IBaseResource} + * @param theRightResource the second {@link IBaseResource} + * @return A boolean indicating whether they match. + */ + @SuppressWarnings("rawtypes") + public boolean match(IBaseResource theLeftResource, IBaseResource theRightResource) { + validate(theLeftResource); + validate(theRightResource); + + FhirTerser terser = myFhirContext.newTerser(); + List leftValues = terser.getValues(theLeftResource, myResourcePath, IBase.class); + List rightValues = terser.getValues(theRightResource, myResourcePath, IBase.class); + return match(leftValues, rightValues); + } + + @SuppressWarnings("rawtypes") + private boolean match(List theLeftValues, List theRightValues) { + boolean retval = false; + for (IBase leftValue : theLeftValues) { + for (IBase rightValue : theRightValues) { + retval |= match(leftValue, rightValue); + } + } + return retval; + } + + private boolean match(IBase theLeftValue, IBase theRightValue) { + return myEmpiFieldMatchJson.getMetric().similarity(myFhirContext, theLeftValue, theRightValue) >= myEmpiFieldMatchJson.getMatchThreshold(); + } + + private void validate(IBaseResource theResource) { + String resourceType = myFhirContext.getResourceType(theResource); + Validate.notNull(resourceType, "Resource type may not be null"); + if (ALL_RESOURCE_SEARCH_PARAM_TYPE.equals(myResourceType)) { + Validate.isTrue("Patient".equalsIgnoreCase(resourceType) || "Practitioner".equalsIgnoreCase(resourceType), + "Expecting resource type Patient/Practitioner got resource type %s", resourceType); + } else { + Validate.isTrue(myResourceType.equals(resourceType), "Expecting resource type %s got resource type %s", myResourceType, resourceType); + } + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/AssuranceLevelUtil.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/AssuranceLevelUtil.java new file mode 100644 index 00000000000..746d1be31c1 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/AssuranceLevelUtil.java @@ -0,0 +1,71 @@ +package ca.uhn.fhir.empi.util; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiLinkSourceEnum; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +/** + * Helper class to determine assurance level based on Link Source and Match Result. + * This is strictly for use in populating Person links. + */ +public final class AssuranceLevelUtil { + + private AssuranceLevelUtil() { + } + + public static CanonicalIdentityAssuranceLevel getAssuranceLevel(EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theSource) { + switch (theSource) { + case MANUAL: + return getAssuranceFromManualResult(theMatchResult); + case AUTO: + return getAssuranceFromAutoResult(theMatchResult); + } + throw new InvalidRequestException("Couldn't figure out an assurance level for result: " + theMatchResult + " and source " + theSource); + } + + private static CanonicalIdentityAssuranceLevel getAssuranceFromAutoResult(EmpiMatchResultEnum theMatchResult) { + switch (theMatchResult) { + case MATCH: + return CanonicalIdentityAssuranceLevel.LEVEL3; + case POSSIBLE_MATCH: + return CanonicalIdentityAssuranceLevel.LEVEL2; + case POSSIBLE_DUPLICATE: + case NO_MATCH: + default: + throw new InvalidRequestException("An AUTO EMPI Link may not have a match result of " + theMatchResult); + } + } + + private static CanonicalIdentityAssuranceLevel getAssuranceFromManualResult(EmpiMatchResultEnum theMatchResult) { + switch (theMatchResult) { + case MATCH: + return CanonicalIdentityAssuranceLevel.LEVEL4; + case NO_MATCH: + case POSSIBLE_DUPLICATE: + case POSSIBLE_MATCH: + default: + throw new InvalidRequestException("A MANUAL EMPI Link may not have a match result of " + theMatchResult); + } + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/EIDHelper.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/EIDHelper.java new file mode 100644 index 00000000000..ff6141f1fb8 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/EIDHelper.java @@ -0,0 +1,114 @@ +package ca.uhn.fhir.empi.util; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiConstants; +import ca.uhn.fhir.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.model.CanonicalEID; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +public final class EIDHelper { + private final FhirContext myFhirContext; + private final IEmpiSettings myEmpiConfig; + + @Autowired + public EIDHelper(FhirContext theFhirContext, IEmpiSettings theEmpiConfig) { + myFhirContext = theFhirContext; + myEmpiConfig = theEmpiConfig; + } + + public CanonicalEID createHapiEid() { + return new CanonicalEID( + EmpiConstants.HAPI_ENTERPRISE_IDENTIFIER_SYSTEM, + UUID.randomUUID().toString(), + null + ); + } + + /** + * Given an {@link IAnyResource} representing a patient/practitioner/person, retrieve their externally-assigned EID, + * represented as a {@link CanonicalEID} + * + * @param theResource the resource to extract the EID from. + * + * @return An optional {@link CanonicalEID} representing the external EID. Absent if the EID is not present. + */ + public List getExternalEid(IBaseResource theResource) { + return CanonicalEID.extractFromResource(myFhirContext, myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem(), theResource); + } + + /** + * Given an {@link IAnyResource} representing a patient/practitioner/person, retrieve their internally-assigned EID, + * represented as a {@link CanonicalEID} + * + * @param theResource the resource to extract the EID from. + * + * @return An optional {@link CanonicalEID} representing the internal EID. Absent if the EID is not present. + */ + public List getHapiEid(IAnyResource theResource) { + return CanonicalEID.extractFromResource(myFhirContext, EmpiConstants.HAPI_ENTERPRISE_IDENTIFIER_SYSTEM, theResource); + } + + /** + * Determines whether two lists of {@link CanonicalEID} have any intersection. Two resources are considered a match if + * a single {@link CanonicalEID} matches between the two collections. + * + * @param theFirstResourceEids the first EID + * @param theSecondResourceEids the second EID + * + * @return a boolean indicating whether there is a match between these two identifier sets. + */ + public boolean eidMatchExists(List theFirstResourceEids, List theSecondResourceEids) { + List collect = theFirstResourceEids.stream().map(CanonicalEID::getValue).collect(Collectors.toList()); + List collect1 = theSecondResourceEids.stream().map(CanonicalEID::getValue).collect(Collectors.toList()); + return !Collections.disjoint( + collect, + collect1 + ); + } + + /** + * An incoming resource is a potential duplicate if it matches a Patient that has a Person with an official EID, but + * the incoming resource also has an EID that does not match. + * + * @param theExistingPerson + * @param theComparingPerson + * @return + */ + public boolean hasEidOverlap(IAnyResource theExistingPerson, IAnyResource theComparingPerson) { + List firstEids = this.getExternalEid(theExistingPerson); + List secondEids = this.getExternalEid(theComparingPerson); + if (firstEids.isEmpty() || secondEids.isEmpty()) { + return false; + } + return this.eidMatchExists(firstEids, secondEids); + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/EmpiUtil.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/EmpiUtil.java new file mode 100644 index 00000000000..0a6f27bc93e --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/EmpiUtil.java @@ -0,0 +1,60 @@ +package ca.uhn.fhir.empi.util; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiConstants; +import org.hl7.fhir.instance.model.api.IBaseResource; + +public final class EmpiUtil { + private EmpiUtil() {} + + public static boolean supportedTargetType(String theResourceType) { + return ("Patient".equals(theResourceType) || "Practitioner".equals(theResourceType)); + } + + public static boolean isEmpiResourceType(FhirContext theFhirContext, IBaseResource theResource) { + String resourceType = theFhirContext.getResourceType(theResource); + return ("Patient".equals(resourceType) || + "Practitioner".equals(resourceType)) || + "Person".equals(resourceType); + } + + /** + * If the resource is tagged as not managed by empi, return false. Otherwise true. + * @param theBaseResource The Patient/Practitioner that is potentially managed by EMPI. + * @return A boolean indicating whether EMPI should manage this resource. + */ + public static boolean isEmpiAccessible(IBaseResource theBaseResource) { + return theBaseResource.getMeta().getTag(EmpiConstants.SYSTEM_EMPI_MANAGED, EmpiConstants.CODE_NO_EMPI_MANAGED) == null; + } + + /** + * Checks for the presence of the EMPI-managed tag, indicating the EMPI system has ownership + * of this Person's links. + * + * @param theBaseResource the resource to check. + * @return a boolean indicating whether or not EMPI manages this Person. + */ + public static boolean isEmpiManaged(IBaseResource theBaseResource) { + return theBaseResource.getMeta().getTag(EmpiConstants.SYSTEM_EMPI_MANAGED, EmpiConstants.CODE_HAPI_EMPI_MANAGED) != null; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/NameUtil.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/NameUtil.java new file mode 100644 index 00000000000..5a340ff471c --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/NameUtil.java @@ -0,0 +1,60 @@ +package ca.uhn.fhir.empi.util; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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 org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.r4.model.HumanName; +import org.hl7.fhir.r4.model.PrimitiveType; + +import java.util.List; +import java.util.stream.Collectors; + +public class NameUtil { + public static List extractGivenNames(FhirContext theFhirContext, IBase theBase) { + switch(theFhirContext.getVersion().getVersion()) { + case R4: + HumanName humanNameR4 = (HumanName)theBase; + return humanNameR4.getGiven().stream().map(PrimitiveType::getValueAsString).filter(s -> !StringUtils.isEmpty(s)).collect(Collectors.toList()); + case DSTU3: + org.hl7.fhir.dstu3.model.HumanName humanNameDSTU3 = (org.hl7.fhir.dstu3.model.HumanName) theBase; + return humanNameDSTU3.getGiven().stream().map(given -> given.toString()).filter(s -> !StringUtils.isEmpty(s)).collect(Collectors.toList()); + default: + throw new UnsupportedOperationException("Version not supported: " + theFhirContext.getVersion().getVersion()); + + } + } + + public static String extractFamilyName(FhirContext theFhirContext, IBase theBase) { + switch(theFhirContext.getVersion().getVersion()) { + case R4: + HumanName humanNameR4 = (HumanName)theBase; + return humanNameR4.getFamily(); + case DSTU3: + org.hl7.fhir.dstu3.model.HumanName humanNameDSTU3 = (org.hl7.fhir.dstu3.model.HumanName)theBase; + return humanNameDSTU3.getFamily(); + default: + throw new UnsupportedOperationException("Version not supported: " + theFhirContext.getVersion().getVersion()); + + } + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/PersonHelper.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/PersonHelper.java new file mode 100644 index 00000000000..c782533da39 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/PersonHelper.java @@ -0,0 +1,619 @@ +package ca.uhn.fhir.empi.util; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiConstants; +import ca.uhn.fhir.empi.api.IEmpiSettings; +import ca.uhn.fhir.empi.log.Logs; +import ca.uhn.fhir.empi.model.CanonicalEID; +import ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel; +import ca.uhn.fhir.empi.model.EmpiTransactionContext; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseBackboneElement; +import org.hl7.fhir.instance.model.api.IBaseCoding; +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.Coding; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.Practitioner; +import org.hl7.fhir.r4.model.Reference; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Service +public class PersonHelper { + private static final Logger ourLog = Logs.getEmpiTroubleshootingLog(); + + @Autowired + private IEmpiSettings myEmpiConfig; + @Autowired + private EIDHelper myEIDHelper; + + private final FhirContext myFhirContext; + + @Autowired + public PersonHelper(FhirContext theFhirContext) { + myFhirContext = theFhirContext; + } + + /** + * Given a Person, extract all {@link IIdType}s for the linked targets. + * + * @param thePerson the Person to extract link IDs from. + * @return a Stream of {@link IIdType}. + */ + public Stream getLinkIds(IBaseResource thePerson) { + switch (myFhirContext.getVersion().getVersion()) { + case R4: + Person personR4 = (Person) thePerson; + return personR4.getLink().stream() + .map(Person.PersonLinkComponent::getTarget) + .map(IBaseReference::getReferenceElement) + .map(IIdType::toUnqualifiedVersionless); + case DSTU3: + org.hl7.fhir.dstu3.model.Person personStu3 = (org.hl7.fhir.dstu3.model.Person) thePerson; + return personStu3.getLink().stream() + .map(org.hl7.fhir.dstu3.model.Person.PersonLinkComponent::getTarget) + .map(IBaseReference::getReferenceElement) + .map(IIdType::toUnqualifiedVersionless); + default: + throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion()); + } + } + + /** + * Determine whether or not the given {@link IBaseResource} person contains a link to a particular {@link IIdType} + * + * @param thePerson The person to check + * @param theResourceId The ID to check. + * @return A boolean indicating whether or not there was a contained link. + */ + public boolean containsLinkTo(IBaseResource thePerson, IIdType theResourceId) { + Stream links = getLinkIds(thePerson); + return links.anyMatch(link -> link.getValue().equals(theResourceId.getValue())); + } + + /** + * Create or update a link from source {@link IBaseResource} to the target {@link IIdType}, with the given {@link CanonicalIdentityAssuranceLevel}. + * @param thePerson The person who's link needs to be updated. + * @param theResourceId The target of the link + * @param canonicalAssuranceLevel The level of certainty of this link. + * @param theEmpiTransactionContext + */ + public void addOrUpdateLink(IBaseResource thePerson, IIdType theResourceId, @Nonnull CanonicalIdentityAssuranceLevel canonicalAssuranceLevel, EmpiTransactionContext theEmpiTransactionContext) { + switch (myFhirContext.getVersion().getVersion()) { + case R4: + handleLinkUpdateR4(thePerson, theResourceId, canonicalAssuranceLevel, theEmpiTransactionContext); + break; + case DSTU3: + handleLinkUpdateDSTU3(thePerson, theResourceId, canonicalAssuranceLevel, theEmpiTransactionContext); + break; + default: + throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion()); + } + } + + private void handleLinkUpdateDSTU3(IBaseResource thePerson, IIdType theResourceId, CanonicalIdentityAssuranceLevel theCanonicalAssuranceLevel, EmpiTransactionContext theTransactionLogMessages) { + if (theCanonicalAssuranceLevel == null) { + ourLog.warn("Refusing to update or add a link without an Assurance Level."); + return; + } + + org.hl7.fhir.dstu3.model.Person person = (org.hl7.fhir.dstu3.model.Person) thePerson; + if (!containsLinkTo(thePerson, theResourceId)) { + person.addLink().setTarget(new org.hl7.fhir.dstu3.model.Reference(theResourceId)).setAssurance(theCanonicalAssuranceLevel.toDstu3()); + logLinkAddMessage(thePerson, theResourceId, theCanonicalAssuranceLevel, theTransactionLogMessages); + } else { + person.getLink().stream() + .filter(link -> link.getTarget().getReference().equalsIgnoreCase(theResourceId.getValue())) + .findFirst() + .ifPresent(link -> { + logLinkUpdateMessage(thePerson, theResourceId, theCanonicalAssuranceLevel, theTransactionLogMessages, link.getAssurance().toCode()); + link.setAssurance(theCanonicalAssuranceLevel.toDstu3()); + }); + } + } + + private void logLinkAddMessage(IBaseResource thePerson, IIdType theResourceId, CanonicalIdentityAssuranceLevel theCanonicalAssuranceLevel, EmpiTransactionContext theEmpiTransactionContext) { + theEmpiTransactionContext.addTransactionLogMessage("Creating new link from " + (StringUtils.isBlank(thePerson.getIdElement().toUnqualifiedVersionless().getValue()) ? "new Person" : thePerson.getIdElement().toUnqualifiedVersionless()) + " -> " + theResourceId.toUnqualifiedVersionless() + " with IdentityAssuranceLevel: " + theCanonicalAssuranceLevel.name()); + } + + private void logLinkUpdateMessage(IBaseResource thePerson, IIdType theResourceId, CanonicalIdentityAssuranceLevel canonicalAssuranceLevel, EmpiTransactionContext theEmpiTransactionContext, String theOriginalAssuranceLevel) { + theEmpiTransactionContext.addTransactionLogMessage("Updating link from " + thePerson.getIdElement().toUnqualifiedVersionless() + " -> " + theResourceId.toUnqualifiedVersionless() + ". Changing IdentityAssuranceLevel: " + theOriginalAssuranceLevel + " -> " + canonicalAssuranceLevel.name()); + } + + private void handleLinkUpdateR4(IBaseResource thePerson, IIdType theResourceId, CanonicalIdentityAssuranceLevel canonicalAssuranceLevel, EmpiTransactionContext theEmpiTransactionContext) { + if (canonicalAssuranceLevel == null) { + ourLog.warn("Refusing to update or add a link without an Assurance Level."); + return; + } + + Person person = (Person) thePerson; + if (!containsLinkTo(thePerson, theResourceId)) { + person.addLink().setTarget(new Reference(theResourceId)).setAssurance(canonicalAssuranceLevel.toR4()); + logLinkAddMessage(thePerson, theResourceId, canonicalAssuranceLevel, theEmpiTransactionContext); + } else { + person.getLink().stream() + .filter(link -> link.getTarget().getReference().equalsIgnoreCase(theResourceId.getValue())) + .findFirst() + .ifPresent(link -> { + logLinkUpdateMessage(thePerson, theResourceId, canonicalAssuranceLevel, theEmpiTransactionContext, link.getAssurance().toCode()); + link.setAssurance(canonicalAssuranceLevel.toR4()); + }); + } + } + + + /** + * Removes a link from the given {@link IBaseResource} to the target {@link IIdType}. + * @param thePerson The person to remove the link from. + * @param theResourceId The target ID to remove. + * @param theEmpiTransactionContext + */ + public void removeLink(IBaseResource thePerson, IIdType theResourceId, EmpiTransactionContext theEmpiTransactionContext) { + if (!containsLinkTo(thePerson, theResourceId)) { + return; + } + theEmpiTransactionContext.addTransactionLogMessage("Removing PersonLinkComponent from " + thePerson.getIdElement().toUnqualifiedVersionless() + " -> " + theResourceId.toUnqualifiedVersionless()); + switch (myFhirContext.getVersion().getVersion()) { + case R4: + Person person = (Person) thePerson; + List links = person.getLink(); + links.removeIf(component -> component.hasTarget() && component.getTarget().getReference().equals(theResourceId.getValue())); + break; + case DSTU3: + org.hl7.fhir.dstu3.model.Person personDstu3 = (org.hl7.fhir.dstu3.model.Person) thePerson; + personDstu3.getLink().removeIf(component -> component.hasTarget() && component.getTarget().getReference().equalsIgnoreCase(theResourceId.getValue())); + break; + default: + throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion()); + } + } + + /** + * Create a Person from a given patient. This will carry over the Patient's EID if it exists. If it does not exist, + * a randomly generated UUID EID will be created. + * + * @param theSourceResource The Patient that will be used as the starting point for the person. + * @return the Person that is created. + */ + public IAnyResource createPersonFromEmpiTarget(IAnyResource theSourceResource) { + String eidSystem = myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem(); + List eidsToApply = myEIDHelper.getExternalEid(theSourceResource); + if (eidsToApply.isEmpty()) { + eidsToApply.add(myEIDHelper.createHapiEid()); + } + switch (myFhirContext.getVersion().getVersion()) { + case R4: + Person personR4 = new Person(); + eidsToApply.forEach(eid -> personR4.addIdentifier(eid.toR4())); + personR4.getMeta().addTag((Coding) buildEmpiManagedTag()); + copyEmpiTargetDataIntoPerson(theSourceResource, personR4, true); + return personR4; + case DSTU3: + org.hl7.fhir.dstu3.model.Person personDSTU3 = new org.hl7.fhir.dstu3.model.Person(); + eidsToApply.forEach(eid -> personDSTU3.addIdentifier(eid.toDSTU3())); + personDSTU3.getMeta().addTag((org.hl7.fhir.dstu3.model.Coding) buildEmpiManagedTag()); + copyEmpiTargetDataIntoPerson(theSourceResource, personDSTU3, true); + return personDSTU3; + default: + throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion()); + } + } + + /** + * This will copy over all attributes that are copiable from Patient/Practitioner to Person. + * + * @param theBaseResource The incoming {@link Patient} or {@link Practitioner} who's data we want to copy into Person. + * @param thePerson The incoming {@link Person} who needs to have their data updated. + * @param theAllowOverwriting If enabled, will overwrite existing values on the person. Otherwise, will set them only if they are currently empty/null. + * + */ + private void copyEmpiTargetDataIntoPerson(IBaseResource theBaseResource, IBaseResource thePerson, Boolean theAllowOverwriting) { + switch (myFhirContext.getVersion().getVersion()) { + case R4: + copyR4TargetInformation(theBaseResource, thePerson, theAllowOverwriting); + break; + case DSTU3: + copyDSTU3TargetInformation(theBaseResource, thePerson, theAllowOverwriting); + break; + default: + throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion()); + } + } + + private void copyR4TargetInformation(IBaseResource theBaseResource, IBaseResource thePerson, boolean theAllowOverwriting) { + Person person = (Person) thePerson; + switch (myFhirContext.getResourceType(theBaseResource)) { + case "Patient": + Patient patient = (Patient) theBaseResource; + if (theAllowOverwriting || person.getName().isEmpty()) { + person.setName(patient.getName()); + } + if (theAllowOverwriting || person.getName().isEmpty()) { + person.setAddress(patient.getAddress()); + } + if (theAllowOverwriting || person.getTelecom().isEmpty()) { + person.setTelecom(patient.getTelecom()); + } + if (theAllowOverwriting || person.getBirthDate() == null) { + person.setBirthDate(patient.getBirthDate()); + } + if (theAllowOverwriting || person.getGender() == null) { + person.setGender(patient.getGender()); + } + if (theAllowOverwriting || person.getPhoto().isEmpty()) { + person.setPhoto(patient.getPhotoFirstRep()); + } + break; + case "Practitioner": + Practitioner practitioner = (Practitioner) theBaseResource; + if (theAllowOverwriting || person.getName().isEmpty()) { + person.setName(practitioner.getName()); + } + if (theAllowOverwriting || person.getAddress().isEmpty()) { + person.setAddress(practitioner.getAddress()); + } + if (theAllowOverwriting || person.getTelecom().isEmpty()) { + person.setTelecom(practitioner.getTelecom()); + } + if (theAllowOverwriting || person.getBirthDate() == null) { + person.setBirthDate(practitioner.getBirthDate()); + } + if (theAllowOverwriting || person.getGender() == null) { + person.setGender(practitioner.getGender()); + } + if (theAllowOverwriting || person.getPhoto().isEmpty()) { + person.setPhoto(practitioner.getPhotoFirstRep()); + } + break; + default: + throw new UnsupportedOperationException("EMPI targets are limited to Practitioner/Patient. This is a : " + myFhirContext.getResourceType(theBaseResource)); + } + } + + private void copyDSTU3TargetInformation(IBaseResource theBaseResource, IBaseResource thePerson, boolean theAllowOverwriting) { + org.hl7.fhir.dstu3.model.Person person = (org.hl7.fhir.dstu3.model.Person) thePerson; + switch (myFhirContext.getResourceType(theBaseResource)) { + case "Patient": + org.hl7.fhir.dstu3.model.Patient patient = (org.hl7.fhir.dstu3.model.Patient) theBaseResource; + + if (theAllowOverwriting || person.getName().isEmpty()) { + person.setName(patient.getName()); + } + if (theAllowOverwriting || person.getAddress().isEmpty()) { + person.setAddress(patient.getAddress()); + } + if (theAllowOverwriting || person.getTelecom().isEmpty()) { + person.setTelecom(patient.getTelecom()); + } + if (theAllowOverwriting || person.getBirthDate() == null ) { + person.setBirthDate(patient.getBirthDate()); + } + if (theAllowOverwriting || person.getGender() == null ) { + person.setGender(patient.getGender()); + } + if (theAllowOverwriting || person.getPhoto().isEmpty()) { + person.setPhoto(patient.getPhotoFirstRep()); + } + break; + case "Practitioner": + org.hl7.fhir.dstu3.model.Practitioner practitioner = (org.hl7.fhir.dstu3.model.Practitioner) theBaseResource; + if (theAllowOverwriting || person.getName().isEmpty()) { + person.setName(practitioner.getName()); + } + if (theAllowOverwriting || person.getAddress().isEmpty()) { + person.setAddress(practitioner.getAddress()); + } + if (theAllowOverwriting || person.getTelecom().isEmpty()) { + person.setTelecom(practitioner.getTelecom()); + } + if (theAllowOverwriting || person.getBirthDate() == null) { + person.setBirthDate(practitioner.getBirthDate()); + } + if (theAllowOverwriting || person.getGender() == null) { + person.setGender(practitioner.getGender()); + } + if (theAllowOverwriting || person.getPhoto().isEmpty()) { + person.setPhoto(practitioner.getPhotoFirstRep()); + } + break; + default: + throw new UnsupportedOperationException("EMPI targets are limited to Practitioner/Patient. This is a : " + myFhirContext.getResourceType(theBaseResource)); + } + } + + private IBaseCoding buildEmpiManagedTag() { + switch (myFhirContext.getVersion().getVersion()) { + case R4: + Coding empiManagedCoding = new Coding(); + empiManagedCoding.setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED); + empiManagedCoding.setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED); + empiManagedCoding.setDisplay(EmpiConstants.DISPLAY_HAPI_EMPI_MANAGED); + return empiManagedCoding; + case DSTU3: + org.hl7.fhir.dstu3.model.Coding empiManagedCodingDstu3 = new org.hl7.fhir.dstu3.model.Coding(); + empiManagedCodingDstu3.setSystem(EmpiConstants.SYSTEM_EMPI_MANAGED); + empiManagedCodingDstu3.setCode(EmpiConstants.CODE_HAPI_EMPI_MANAGED); + empiManagedCodingDstu3.setDisplay(EmpiConstants.DISPLAY_HAPI_EMPI_MANAGED); + return empiManagedCodingDstu3; + default: + throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion()); + + } + } + + /** + * Update a Person's EID based on the incoming target resource. If the incoming resource has an external EID, it is applied + * to the Person, unless that person already has an external EID which does not match, in which case throw {@link IllegalArgumentException} + * + * If running in multiple EID mode, then incoming EIDs are simply added to the Person without checking for matches. + * + * @param thePerson The person to update the external EID on. + * @param theEmpiTarget The target we will retrieve the external EID from. + * @return the modified {@link IBaseResource} representing the person. + */ + public IAnyResource updatePersonExternalEidFromEmpiTarget(IAnyResource thePerson, IAnyResource theEmpiTarget, EmpiTransactionContext theEmpiTransactionContext) { + //This handles overwriting an automatically assigned EID if a patient that links is coming in with an official EID. + List incomingTargetEid = myEIDHelper.getExternalEid(theEmpiTarget); + List personOfficialEid = myEIDHelper.getExternalEid(thePerson); + + + if (!incomingTargetEid.isEmpty()) { + if (personOfficialEid.isEmpty() || !myEmpiConfig.isPreventMultipleEids()) { + log(theEmpiTransactionContext, "Incoming resource:" + theEmpiTarget.getIdElement().toUnqualifiedVersionless() + " + with EID " + incomingTargetEid.stream().map(CanonicalEID::toString).collect(Collectors.joining(",")) + " is applying this EIDs to its related Person, as this person does not yet have an external EID"); + addCanonicalEidsToPersonIfAbsent(thePerson, incomingTargetEid); + } else if (!personOfficialEid.isEmpty() && myEIDHelper.eidMatchExists(personOfficialEid, incomingTargetEid)) { + log(theEmpiTransactionContext, "incoming resource:" + theEmpiTarget.getIdElement().toVersionless() + " with EIDs "+incomingTargetEid.stream().map(CanonicalEID::toString).collect(Collectors.joining(",")) +" does not need to overwrite person, as this EID is already present"); + } else { + throw new IllegalArgumentException("This would create a duplicate person!"); + } + } + return thePerson; + } + + public IBaseResource overwriteExternalEids(IBaseResource thePerson, List theNewEid) { + clearExternalEids(thePerson); + addCanonicalEidsToPersonIfAbsent(thePerson, theNewEid); + return thePerson; + } + + private void clearExternalEids(IBaseResource thePerson) { + switch (myFhirContext.getVersion().getVersion()) { + case R4: + Person personR4 = (Person) thePerson; + personR4.getIdentifier().removeIf(theIdentifier -> theIdentifier.getSystem().equalsIgnoreCase(myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem())); + break; + case DSTU3: + org.hl7.fhir.dstu3.model.Person personDstu3 = (org.hl7.fhir.dstu3.model.Person) thePerson; + personDstu3.getIdentifier().removeIf(theIdentifier -> theIdentifier.getSystem().equalsIgnoreCase(myEmpiConfig.getEmpiRules().getEnterpriseEIDSystem())); + break; + default: + throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion()); + } + } + + private void addCanonicalEidsToPersonIfAbsent(IBaseResource thePerson, List theIncomingTargetEid) { + switch (myFhirContext.getVersion().getVersion()) { + case R4: + theIncomingTargetEid.forEach(eid -> addIdentifierIfAbsent((Person) thePerson, eid.toR4())); + break; + case DSTU3: + theIncomingTargetEid.forEach(eid -> addIdentifierIfAbsent((org.hl7.fhir.dstu3.model.Person) thePerson, eid.toDSTU3())); + break; + default: + throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion()); + } + } + + /** + * To avoid adding duplicate + * + * @param thePerson + * @param theIdentifier + */ + private void addIdentifierIfAbsent(org.hl7.fhir.dstu3.model.Person thePerson, org.hl7.fhir.dstu3.model.Identifier theIdentifier) { + Optional first = thePerson.getIdentifier().stream().filter(identifier -> identifier.getSystem().equals(theIdentifier.getSystem())).filter(identifier -> identifier.getValue().equals(theIdentifier.getValue())).findFirst(); + if (first.isPresent()) { + return; + } else { + thePerson.addIdentifier(theIdentifier); + } + } + + private void addIdentifierIfAbsent(Person thePerson, Identifier theIdentifier) { + Optional first = thePerson.getIdentifier().stream().filter(identifier -> identifier.getSystem().equals(theIdentifier.getSystem())).filter(identifier -> identifier.getValue().equals(theIdentifier.getValue())).findFirst(); + if (first.isPresent()) { + return; + } else { + thePerson.addIdentifier(theIdentifier); + } + } + + public void mergePersonFields(IBaseResource thePersonToDelete, IBaseResource thePersonToKeep) { + switch (myFhirContext.getVersion().getVersion()) { + case R4: + mergeR4PersonFields(thePersonToDelete, thePersonToKeep); + break; + case DSTU3: + mergeDstu3PersonFields(thePersonToDelete, thePersonToKeep); + break; + default: + throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion()); + } + } + + private void mergeR4PersonFields(IBaseResource thePersonToDelete, IBaseResource thePersonToKeep) { + Person fromPerson = (Person) thePersonToDelete; + Person toPerson = (Person) thePersonToKeep; + if (!toPerson.hasName()) { + toPerson.setName(fromPerson.getName()); + } + if (!toPerson.hasAddress()) { + toPerson.setAddress(fromPerson.getAddress()); + } + if (!toPerson.hasTelecom()) { + toPerson.setTelecom(fromPerson.getTelecom()); + } + if (!toPerson.hasBirthDate()) { + toPerson.setBirthDate(fromPerson.getBirthDate()); + } + if (!toPerson.hasGender()) { + toPerson.setGender(fromPerson.getGender()); + } + if (!toPerson.hasPhoto()) { + toPerson.setPhoto(fromPerson.getPhoto()); + } + } + + + private void mergeDstu3PersonFields(IBaseResource thePersonToDelete, IBaseResource thePersonToKeep) { + org.hl7.fhir.dstu3.model.Person fromPerson = (org.hl7.fhir.dstu3.model.Person) thePersonToDelete; + org.hl7.fhir.dstu3.model.Person toPerson = (org.hl7.fhir.dstu3.model.Person) thePersonToKeep; + if (!toPerson.hasName()) { + toPerson.setName(fromPerson.getName()); + } + if (!toPerson.hasAddress()) { + toPerson.setAddress(fromPerson.getAddress()); + } + if (!toPerson.hasTelecom()) { + toPerson.setTelecom(fromPerson.getTelecom()); + } + if (!toPerson.hasBirthDate()) { + toPerson.setBirthDate(fromPerson.getBirthDate()); + } + if (!toPerson.hasGender()) { + toPerson.setGender(fromPerson.getGender()); + } + if (!toPerson.hasPhoto()) { + toPerson.setPhoto(fromPerson.getPhoto()); + } + } + + /** + * An incoming resource is a potential duplicate if it matches a Patient that has a Person with an official EID, but + * the incoming resource also has an EID that does not match. + * + * @param theExistingPerson + * @param theComparingPerson + * @return + */ + public boolean isPotentialDuplicate(IAnyResource theExistingPerson, IAnyResource theComparingPerson) { + List externalEidsPerson = myEIDHelper.getExternalEid(theExistingPerson); + List externalEidsResource = myEIDHelper.getExternalEid(theComparingPerson); + return !externalEidsPerson.isEmpty() && !externalEidsResource.isEmpty() && !myEIDHelper.eidMatchExists(externalEidsResource, externalEidsPerson); + } + + public IBaseBackboneElement newPersonLink(IIdType theTargetId, CanonicalIdentityAssuranceLevel theAssuranceLevel) { + switch (myFhirContext.getVersion().getVersion()) { + case R4: + return newR4PersonLink(theTargetId, theAssuranceLevel); + case DSTU3: + return newDstu3PersonLink(theTargetId, theAssuranceLevel); + default: + throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion()); + } + } + + private IBaseBackboneElement newR4PersonLink(IIdType theTargetId, CanonicalIdentityAssuranceLevel theAssuranceLevel) { + Person.PersonLinkComponent retval = new Person.PersonLinkComponent(); + retval.setTarget(new Reference(theTargetId)); + retval.setAssurance(theAssuranceLevel.toR4()); + return retval; + } + + private IBaseBackboneElement newDstu3PersonLink(IIdType theTargetId, CanonicalIdentityAssuranceLevel theAssuranceLevel) { + org.hl7.fhir.dstu3.model.Person.PersonLinkComponent retval = new org.hl7.fhir.dstu3.model.Person.PersonLinkComponent(); + retval.setTarget(new org.hl7.fhir.dstu3.model.Reference(theTargetId)); + retval.setAssurance(theAssuranceLevel.toDstu3()); + return retval; + } + + public void setLinks(IAnyResource thePersonResource, List theNewLinks) { + switch (myFhirContext.getVersion().getVersion()) { + case R4: + setLinksR4(thePersonResource, theNewLinks); + break; + case DSTU3: + setLinksDstu3(thePersonResource, theNewLinks); + break; + default: + throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion()); + } + } + + private void setLinksDstu3(IAnyResource thePersonResource, List theLinks) { + org.hl7.fhir.dstu3.model.Person person = (org.hl7.fhir.dstu3.model.Person)thePersonResource; + List links = (List)(List)theLinks; + person.setLink(links); + } + + private void setLinksR4(IAnyResource thePersonResource, List theLinks) { + Person person = (Person)thePersonResource; + List links = (List)(List)theLinks; + person.setLink(links); + } + + public void updatePersonFromNewlyCreatedEmpiTarget(IBaseResource thePerson, IBaseResource theResource, EmpiTransactionContext theEmpiTransactionContext) { + copyEmpiTargetDataIntoPerson(theResource, thePerson, false); + } + + public void updatePersonFromUpdatedEmpiTarget(IBaseResource thePerson, IBaseResource theResource, EmpiTransactionContext theEmpiTransactionContext) { + copyEmpiTargetDataIntoPerson(theResource, thePerson, true); + } + + public int getLinkCount(IAnyResource thePerson) { + switch (myFhirContext.getVersion().getVersion()) { + case R4: + Person personR4 = (Person) thePerson; + return personR4.getLink().size(); + case DSTU3: + org.hl7.fhir.dstu3.model.Person personStu3 = (org.hl7.fhir.dstu3.model.Person) thePerson; + return personStu3.getLink().size(); + default: + throw new UnsupportedOperationException("Version not supported: " + myFhirContext.getVersion().getVersion()); + } + } + + private void log(EmpiTransactionContext theEmpiTransactionContext, String theMessage) { + theEmpiTransactionContext.addTransactionLogMessage(theMessage); + ourLog.debug(theMessage); + } + + public void handleExternalEidAddition(IAnyResource thePerson, IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext) { + List eidFromResource = myEIDHelper.getExternalEid(theResource); + if (!eidFromResource.isEmpty()) { + updatePersonExternalEidFromEmpiTarget(thePerson, theResource, theEmpiTransactionContext); + } + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/rest/server/TransactionLogMessages.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/rest/server/TransactionLogMessages.java new file mode 100644 index 00000000000..03fa2d1e0b7 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/rest/server/TransactionLogMessages.java @@ -0,0 +1,59 @@ +package ca.uhn.fhir.rest.server; + +/*- + * #%L + * HAPI FHIR - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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 java.util.ArrayList; +import java.util.List; + +public class TransactionLogMessages { + private List myMessages; + private final String myTransactionGuid; + + private TransactionLogMessages(String theTransactionGuid) { + myTransactionGuid = theTransactionGuid; + } + + public static TransactionLogMessages createFromTransactionGuid(String theTransactionGuid) { + return new TransactionLogMessages(theTransactionGuid); + } + + private void addMessage(String theMessage) { + if (myMessages == null) { + myMessages = new ArrayList<>(); + } + myMessages.add(theMessage); + } + + public List getValues() { + return myMessages; + } + + public static void addMessage(TransactionLogMessages theTransactionLogMessages, String theMessage) { + if (theTransactionLogMessages == null) { + return; + } + theTransactionLogMessages.addMessage(theMessage); + } + + public String getTransactionGuid() { + return myTransactionGuid; + } +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/BaseR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/BaseR4Test.java new file mode 100644 index 00000000000..71e55e90631 --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/BaseR4Test.java @@ -0,0 +1,89 @@ +package ca.uhn.fhir.empi; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.rules.config.EmpiSettings; +import ca.uhn.fhir.empi.rules.json.DistanceMetricEnum; +import ca.uhn.fhir.empi.rules.json.EmpiFieldMatchJson; +import ca.uhn.fhir.empi.rules.json.EmpiFilterSearchParamJson; +import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson; +import ca.uhn.fhir.empi.rules.json.EmpiRulesJson; +import ca.uhn.fhir.empi.rules.svc.EmpiResourceComparatorSvc; +import org.hl7.fhir.r4.model.Patient; +import org.junit.Before; + +public abstract class BaseR4Test { + protected static final FhirContext ourFhirContext = FhirContext.forR4(); + public static final String PATIENT_GIVEN = "patient-given"; + public static final String PATIENT_LAST = "patient-last"; + public static final String PATIENT_GENERAL_PRACTITIONER= "patient-practitioner"; + + + public static final double NAME_THRESHOLD = 0.8; + protected EmpiFieldMatchJson myGivenNameMatchField; + protected EmpiFieldMatchJson myParentMatchField; + protected String myBothNameFields; + + @Before + public void before() { + myGivenNameMatchField = new EmpiFieldMatchJson() + .setName(PATIENT_GIVEN) + .setResourceType("Patient") + .setResourcePath("name.given") + .setMetric(DistanceMetricEnum.COSINE) + .setMatchThreshold(NAME_THRESHOLD); + myBothNameFields = String.join(",", PATIENT_GIVEN, PATIENT_LAST); + } + + protected Patient buildJohn() { + Patient patient = new Patient(); + patient.addName().addGiven("John"); + patient.setId("Patient/1"); + return patient; + } + + protected Patient buildJohny() { + Patient patient = new Patient(); + patient.addName().addGiven("Johny"); + patient.setId("Patient/2"); + return patient; + } + + protected EmpiRulesJson buildActiveBirthdateIdRules() { + EmpiFilterSearchParamJson activePatientsBlockingFilter = new EmpiFilterSearchParamJson() + .setResourceType("Patient") + .setSearchParam(Patient.SP_ACTIVE) + .setFixedValue("true"); + + EmpiResourceSearchParamJson patientBirthdayBlocking = new EmpiResourceSearchParamJson() + .setResourceType("Patient") + .setSearchParam(Patient.SP_BIRTHDATE); + EmpiResourceSearchParamJson patientIdentifierBlocking = new EmpiResourceSearchParamJson() + .setResourceType("Patient") + .setSearchParam(Patient.SP_IDENTIFIER); + + + EmpiFieldMatchJson lastNameMatchField = new EmpiFieldMatchJson() + .setName(PATIENT_LAST) + .setResourceType("Patient") + .setResourcePath("name.family") + .setMetric(DistanceMetricEnum.JARO_WINKLER) + .setMatchThreshold(NAME_THRESHOLD); + + EmpiRulesJson retval = new EmpiRulesJson(); + retval.addResourceSearchParam(patientBirthdayBlocking); + retval.addResourceSearchParam(patientIdentifierBlocking); + retval.addFilterSearchParam(activePatientsBlockingFilter); + retval.addMatchField(myGivenNameMatchField); + retval.addMatchField(lastNameMatchField); + retval.putMatchResult(myBothNameFields, EmpiMatchResultEnum.MATCH); + retval.putMatchResult(PATIENT_GIVEN, EmpiMatchResultEnum.POSSIBLE_MATCH); + return retval; + } + + protected EmpiResourceComparatorSvc buildComparator(EmpiRulesJson theEmpiRulesJson) { + EmpiResourceComparatorSvc retval = new EmpiResourceComparatorSvc(ourFhirContext, new EmpiSettings().setEmpiRules(theEmpiRulesJson)); + retval.init(); + return retval; + } +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/config/EmpiRuleValidatorTest.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/config/EmpiRuleValidatorTest.java new file mode 100644 index 00000000000..19f1576f57d --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/config/EmpiRuleValidatorTest.java @@ -0,0 +1,47 @@ +package ca.uhn.fhir.empi.rules.config; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.empi.rules.json.EmpiRulesJson; +import com.google.common.base.Charsets; +import org.apache.commons.io.IOUtils; +import org.junit.Test; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; + +import java.io.IOException; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class EmpiRuleValidatorTest { + private EmpiRuleValidator myEmpiRuleValidator = new EmpiRuleValidator(); + + @Test + public void testValidate() { + String invalidUri = "invalid uri"; + EmpiRulesJson sampleEmpiRulesJson = new EmpiRulesJson(); + sampleEmpiRulesJson.setEnterpriseEIDSystem(invalidUri); + + try { + myEmpiRuleValidator.validate(sampleEmpiRulesJson); + fail(); + } catch (ConfigurationException e){ + assertThat(e.getMessage(), is("Enterprise Identifier System (eidSystem) must be a valid URI")); + } + } + + @Test + public void testNonExistentMatchField() throws IOException { + EmpiSettings empiSettings = new EmpiSettings(); + DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); + Resource resource = resourceLoader.getResource("bad-rules.json"); + String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8); + try { + empiSettings.setScriptText(json); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), is("There is no matchField with name foo")); + } + } +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJsonR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJsonR4Test.java new file mode 100644 index 00000000000..683deda7f22 --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJsonR4Test.java @@ -0,0 +1,61 @@ +package ca.uhn.fhir.empi.rules.json; + +import ca.uhn.fhir.empi.BaseR4Test; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.util.JsonUtil; +import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.fail; + +public class EmpiRulesJsonR4Test extends BaseR4Test { + private static final Logger ourLog = LoggerFactory.getLogger(EmpiRulesJsonR4Test.class); + private EmpiRulesJson myRules; + + @Before + public void before() { + super.before(); + + myRules = buildActiveBirthdateIdRules(); + } + + @Test + public void testSerDeser() throws IOException { + String json = JsonUtil.serialize(myRules); + ourLog.info(json); + EmpiRulesJson rulesDeser = JsonUtil.deserialize(json, EmpiRulesJson.class); + assertEquals(2, rulesDeser.size()); + assertEquals(EmpiMatchResultEnum.MATCH, rulesDeser.getMatchResult(myBothNameFields)); + EmpiFieldMatchJson second = rulesDeser.get(1); + assertEquals("name.family", second.getResourcePath()); + TestCase.assertEquals(DistanceMetricEnum.JARO_WINKLER, second.getMetric()); + } + + @Test + public void testMatchResultMap() { + assertEquals(EmpiMatchResultEnum.MATCH, myRules.getMatchResult(3L)); + } + + @Test + public void getVector() { + VectorMatchResultMap vectorMatchResultMap = myRules.getVectorMatchResultMapForUnitTest(); + assertEquals(1, vectorMatchResultMap.getVector(PATIENT_GIVEN)); + assertEquals(2, vectorMatchResultMap.getVector(PATIENT_LAST)); + assertEquals(3, vectorMatchResultMap.getVector(String.join(",", PATIENT_GIVEN, PATIENT_LAST))); + assertEquals(3, vectorMatchResultMap.getVector(String.join(", ", PATIENT_GIVEN, PATIENT_LAST))); + assertEquals(3, vectorMatchResultMap.getVector(String.join(", ", PATIENT_GIVEN, PATIENT_LAST))); + assertEquals(3, vectorMatchResultMap.getVector(String.join(", \n ", PATIENT_GIVEN, PATIENT_LAST))); + try { + vectorMatchResultMap.getVector("bad"); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("There is no matchField with name bad", e.getMessage()); + } + } +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/json/VectorMatchResultMapTest.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/json/VectorMatchResultMapTest.java new file mode 100644 index 00000000000..502d1530c51 --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/json/VectorMatchResultMapTest.java @@ -0,0 +1,24 @@ +package ca.uhn.fhir.empi.rules.json; + +import org.junit.Test; + +import static junit.framework.TestCase.assertEquals; + +public class VectorMatchResultMapTest { + @Test + public void splitFieldMatchNames() { + { + String[] result = VectorMatchResultMap.splitFieldMatchNames("a,b"); + assertEquals(2, result.length); + assertEquals("a", result[0]); + assertEquals("b", result[1]); + } + + { + String[] result = VectorMatchResultMap.splitFieldMatchNames("a, b"); + assertEquals(2, result.length); + assertEquals("a", result[0]); + assertEquals("b", result[1]); + } + } +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/CustomResourceComparatorR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/CustomResourceComparatorR4Test.java new file mode 100644 index 00000000000..c0ee1ae0352 --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/CustomResourceComparatorR4Test.java @@ -0,0 +1,117 @@ +package ca.uhn.fhir.empi.rules.svc; + +import ca.uhn.fhir.empi.BaseR4Test; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import ca.uhn.fhir.empi.rules.json.DistanceMetricEnum; +import ca.uhn.fhir.empi.rules.json.EmpiFieldMatchJson; +import ca.uhn.fhir.empi.rules.json.EmpiRulesJson; +import org.hl7.fhir.r4.model.HumanName; +import org.hl7.fhir.r4.model.Patient; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class CustomResourceComparatorR4Test extends BaseR4Test { + + public static final String FIELD_EXACT_MATCH_NAME = DistanceMetricEnum.EXACT_NAME_ANY_ORDER.name(); + private static Patient ourJohnHenry; + private static Patient ourJohnHENRY; + private static Patient ourJaneHenry; + private static Patient ourJohnSmith; + private static Patient ourJohnBillyHenry; + private static Patient ourBillyJohnHenry; + private static Patient ourHenryJohn; + private static Patient ourHenryJOHN; + + @BeforeClass + public static void beforeClass() { + ourJohnHenry = buildPatientWithNames("Henry", "John"); + ourJohnHENRY = buildPatientWithNames("HENRY", "John"); + ourJaneHenry = buildPatientWithNames("Henry", "Jane"); + ourJohnSmith = buildPatientWithNames("Smith", "John"); + ourJohnBillyHenry = buildPatientWithNames("Henry", "John", "Billy"); + ourBillyJohnHenry = buildPatientWithNames("Henry", "Billy", "John"); + ourHenryJohn = buildPatientWithNames("John", "Henry"); + ourHenryJOHN = buildPatientWithNames("JOHN", "Henry"); + } + + @Test + public void testExactNameAnyOrder() { + EmpiResourceComparatorSvc nameAnyOrderComparator = buildComparator(buildNameAnyOrderRules(DistanceMetricEnum.EXACT_NAME_ANY_ORDER)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnHenry)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourHenryJohn)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourHenryJOHN)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnHENRY)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJaneHenry)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnSmith)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnBillyHenry)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourBillyJohnHenry)); + } + + @Test + public void testStandardNameAnyOrder() { + EmpiResourceComparatorSvc nameAnyOrderComparator = buildComparator(buildNameAnyOrderRules(DistanceMetricEnum.STANDARD_NAME_ANY_ORDER)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnHenry)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourHenryJohn)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourHenryJOHN)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnHENRY)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJaneHenry)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnSmith)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnBillyHenry)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourBillyJohnHenry)); + } + + + @Test + public void testExactNameFirstAndLast() { + EmpiResourceComparatorSvc nameAnyOrderComparator = buildComparator(buildNameAnyOrderRules(DistanceMetricEnum.EXACT_NAME_FIRST_AND_LAST)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnHenry)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourHenryJohn)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourHenryJOHN)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnHENRY)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJaneHenry)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnSmith)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnBillyHenry)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourBillyJohnHenry)); + } + + @Test + public void testStandardNameFirstAndLast() { + EmpiResourceComparatorSvc nameAnyOrderComparator = buildComparator(buildNameAnyOrderRules(DistanceMetricEnum.STANDARD_NAME_FIRST_AND_LAST)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnHenry)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourHenryJohn)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourHenryJOHN)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnHENRY)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJaneHenry)); + assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnSmith)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourJohnBillyHenry)); + assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderComparator.compare(ourJohnHenry, ourBillyJohnHenry)); + } + + protected static Patient buildPatientWithNames(String theFamilyName, String... theGivenNames) { + Patient patient = new Patient(); + HumanName name = patient.addName(); + name.setFamily(theFamilyName); + for (String givenName : theGivenNames) { + name.addGiven(givenName); + } + patient.setId("Patient/1"); + return patient; + } + + private EmpiRulesJson buildNameAnyOrderRules(DistanceMetricEnum theExactNameAnyOrder) { + EmpiFieldMatchJson nameAnyOrderFieldMatch = new EmpiFieldMatchJson() + .setName(FIELD_EXACT_MATCH_NAME) + .setResourceType("Patient") + .setResourcePath("name") + .setMetric(theExactNameAnyOrder) + .setMatchThreshold(NAME_THRESHOLD); + + EmpiRulesJson retval = new EmpiRulesJson(); + retval.addMatchField(nameAnyOrderFieldMatch); + retval.putMatchResult(FIELD_EXACT_MATCH_NAME, EmpiMatchResultEnum.MATCH); + + return retval; + } +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceComparatorSvcR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceComparatorSvcR4Test.java new file mode 100644 index 00000000000..484842cbf70 --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceComparatorSvcR4Test.java @@ -0,0 +1,53 @@ +package ca.uhn.fhir.empi.rules.svc; + +import ca.uhn.fhir.empi.BaseR4Test; +import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; +import org.hl7.fhir.r4.model.Patient; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class EmpiResourceComparatorSvcR4Test extends BaseR4Test { + private EmpiResourceComparatorSvc myEmpiResourceComparatorSvc; + public static final double NAME_DELTA = 0.0001; + + private Patient myJohn; + private Patient myJohny; + + @Before + public void before() { + super.before(); + + myEmpiResourceComparatorSvc = buildComparator(buildActiveBirthdateIdRules()); + + myJohn = buildJohn(); + myJohny = buildJohny(); + } + + @Test + public void testCompareFirstNameMatch() { + EmpiMatchResultEnum result = myEmpiResourceComparatorSvc.compare(myJohn, myJohny); + assertEquals(EmpiMatchResultEnum.POSSIBLE_MATCH, result); + } + + @Test + public void testCompareBothNamesMatch() { + myJohn.addName().setFamily("Smith"); + myJohny.addName().setFamily("Smith"); + EmpiMatchResultEnum result = myEmpiResourceComparatorSvc.compare(myJohn, myJohny); + assertEquals(EmpiMatchResultEnum.MATCH, result); + } + + @Test + public void testMatchResult() { + assertEquals(EmpiMatchResultEnum.POSSIBLE_MATCH, myEmpiResourceComparatorSvc.getMatchResult(myJohn, myJohny)); + myJohn.addName().setFamily("Smith"); + myJohny.addName().setFamily("Smith"); + assertEquals(EmpiMatchResultEnum.MATCH, myEmpiResourceComparatorSvc.getMatchResult(myJohn, myJohny)); + Patient patient3 = new Patient(); + patient3.setId("Patient/3"); + patient3.addName().addGiven("Henry"); + assertEquals(EmpiMatchResultEnum.NO_MATCH, myEmpiResourceComparatorSvc.getMatchResult(myJohn, patient3)); + } +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldComparatorR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldComparatorR4Test.java new file mode 100644 index 00000000000..52b12346952 --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldComparatorR4Test.java @@ -0,0 +1,81 @@ +package ca.uhn.fhir.empi.rules.svc; + +import ca.uhn.fhir.empi.BaseR4Test; +import ca.uhn.fhir.empi.rules.json.DistanceMetricEnum; +import ca.uhn.fhir.empi.rules.json.EmpiFieldMatchJson; +import ca.uhn.fhir.parser.DataFormatException; +import org.hl7.fhir.r4.model.Encounter; +import org.hl7.fhir.r4.model.Patient; +import org.junit.Before; +import org.junit.Test; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; +import static junit.framework.TestCase.fail; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.StringStartsWith.startsWith; + +public class EmpiResourceFieldComparatorR4Test extends BaseR4Test { + protected EmpiResourceFieldComparator myComparator; + private Patient myJohn; + private Patient myJohny; + + @Before + public void before() { + super.before(); + + myComparator = new EmpiResourceFieldComparator(ourFhirContext, myGivenNameMatchField); + myJohn = buildJohn(); + myJohny = buildJohny(); + } + + @Test + public void testSimplePatient() { + Patient patient = new Patient(); + patient.setActive(true); + + assertFalse(myComparator.match(patient, myJohny)); + } + + @Test + public void testBadType() { + Encounter encounter = new Encounter(); + encounter.setId("Encounter/1"); + + try { + myComparator.match(encounter, myJohny); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Expecting resource type Patient got resource type Encounter", e.getMessage()); + } + try { + myComparator.match(myJohn, encounter); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Expecting resource type Patient got resource type Encounter", e.getMessage()); + } + } + + @Test + public void testBadPath() { + try { + EmpiFieldMatchJson matchField = new EmpiFieldMatchJson() + .setName("patient-foo") + .setResourceType("Patient") + .setResourcePath("foo") + .setMetric(DistanceMetricEnum.COSINE) + .setMatchThreshold(NAME_THRESHOLD); + EmpiResourceFieldComparator comparator = new EmpiResourceFieldComparator(ourFhirContext, matchField); + comparator.match(myJohn, myJohny); + fail(); + } catch (DataFormatException e) { + assertThat(e.getMessage(), startsWith("Unknown child name 'foo' in element Patient")); + } + } + + @Test + public void testMatch() { + assertTrue(myComparator.match(myJohn, myJohny)); + } +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/EIDHelperR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/EIDHelperR4Test.java new file mode 100644 index 00000000000..8d71d0956bb --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/EIDHelperR4Test.java @@ -0,0 +1,78 @@ +package ca.uhn.fhir.empi.svc; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.model.CanonicalEID; +import ca.uhn.fhir.empi.rules.config.EmpiSettings; +import ca.uhn.fhir.empi.rules.json.EmpiRulesJson; +import ca.uhn.fhir.empi.util.EIDHelper; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Patient; +import org.junit.Test; + +import java.util.List; + +import static ca.uhn.fhir.empi.api.EmpiConstants.HAPI_ENTERPRISE_IDENTIFIER_SYSTEM; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + + +public class EIDHelperR4Test { + + private static final FhirContext myFhirContext = FhirContext.forR4(); + private static final String EXTERNAL_ID_SYSTEM_FOR_TEST = "http://testsystem.io/naming-system/empi"; + + private static final EmpiRulesJson myRules = new EmpiRulesJson() {{ + setEnterpriseEIDSystem(EXTERNAL_ID_SYSTEM_FOR_TEST); + }}; + + private static final EmpiSettings mySettings = new EmpiSettings() {{ + setEmpiRules(myRules); + }}; + + private static final EIDHelper EID_HELPER = new EIDHelper(myFhirContext, mySettings); + + + @Test + public void testExtractionOfInternalEID() { + Patient patient = new Patient(); + patient.addIdentifier() + .setSystem(HAPI_ENTERPRISE_IDENTIFIER_SYSTEM) + .setValue("simpletest") + .setUse(Identifier.IdentifierUse.SECONDARY); + + List externalEid = EID_HELPER.getHapiEid(patient); + + assertThat(externalEid.isEmpty(), is(false)); + assertThat(externalEid.get(0).getValue(), is(equalTo("simpletest"))); + assertThat(externalEid.get(0).getSystem(), is(equalTo(HAPI_ENTERPRISE_IDENTIFIER_SYSTEM))); + assertThat(externalEid.get(0).getUse(), is(equalTo("secondary"))); + } + + @Test + public void testExtractionOfExternalEID() { + String uniqueID = "uniqueID!"; + + Patient patient = new Patient(); + patient.addIdentifier() + .setSystem(EXTERNAL_ID_SYSTEM_FOR_TEST) + .setValue(uniqueID); + + List externalEid = EID_HELPER.getExternalEid(patient); + + assertThat(externalEid.isEmpty(), is(false)); + assertThat(externalEid.get(0).getValue(), is(equalTo(uniqueID))); + assertThat(externalEid.get(0).getSystem(), is(equalTo(EXTERNAL_ID_SYSTEM_FOR_TEST))); + } + + @Test + public void testCreationOfInternalEIDGeneratesUuidEID() { + + CanonicalEID internalEid = EID_HELPER.createHapiEid(); + + assertThat(internalEid.getSystem(), is(equalTo(HAPI_ENTERPRISE_IDENTIFIER_SYSTEM))); + assertThat(internalEid.getValue().length(), is(equalTo(36))); + assertThat(internalEid.getUse(), is(nullValue())); + } +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/NameUtilTestDSTU3.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/NameUtilTestDSTU3.java new file mode 100644 index 00000000000..9c69b4dbde4 --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/NameUtilTestDSTU3.java @@ -0,0 +1,44 @@ +package ca.uhn.fhir.empi.svc; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.util.NameUtil; +import ca.uhn.fhir.util.FhirTerser; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.instance.model.api.IBase; +import org.junit.Test; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; + +public class NameUtilTestDSTU3 { + + private static final FhirContext myFhirContext = FhirContext.forDstu3(); + + @Test + public void testExtractName() { + Patient patient = new Patient(); + patient.getNameFirstRep().setFamily("family"); + patient.getNameFirstRep().getGiven().add(new StringType("given1")); + patient.getNameFirstRep().getGiven().add(new StringType("given2")); + FhirTerser terser = myFhirContext.newTerser(); + List names = terser.getValues(patient, "name", IBase.class); + assertThat(names, hasSize(1)); + IBase name = names.get(0); + + { + String familyName = NameUtil.extractFamilyName(myFhirContext, name ); + assertThat(familyName, is(equalTo("family"))); + } + + { + List familyName = NameUtil.extractGivenNames(myFhirContext, name); + assertThat(familyName, hasItems("given1", "given2")); + } + } +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/NameUtilTestR4.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/NameUtilTestR4.java new file mode 100644 index 00000000000..0e4ba9e53fa --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/NameUtilTestR4.java @@ -0,0 +1,44 @@ +package ca.uhn.fhir.empi.svc; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.util.NameUtil; +import ca.uhn.fhir.util.FhirTerser; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.StringType; +import org.junit.Test; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; + +public class NameUtilTestR4 { + + private static final FhirContext myFhirContext = FhirContext.forR4(); + + @Test + public void testExtractName() { + Patient patient = new Patient(); + patient.getNameFirstRep().setFamily("family"); + patient.getNameFirstRep().getGiven().add(new StringType("given1")); + patient.getNameFirstRep().getGiven().add(new StringType("given2")); + FhirTerser terser = myFhirContext.newTerser(); + List names = terser.getValues(patient, "name", IBase.class); + assertThat(names, hasSize(1)); + IBase name = names.get(0); + + { + String familyName = NameUtil.extractFamilyName(myFhirContext, name ); + assertThat(familyName, is(equalTo("family"))); + } + + { + List familyName = NameUtil.extractGivenNames(myFhirContext, name); + assertThat(familyName, hasItems("given1", "given2")); + } + } +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/PersonHelperDSTU3Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/PersonHelperDSTU3Test.java new file mode 100644 index 00000000000..356a2d438eb --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/PersonHelperDSTU3Test.java @@ -0,0 +1,84 @@ +package ca.uhn.fhir.empi.svc; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel; +import ca.uhn.fhir.empi.util.PersonHelper; +import ca.uhn.fhir.model.primitive.IdDt; +import org.hl7.fhir.dstu3.model.Person; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.Test; + +import java.util.List; +import java.util.stream.Collectors; + +import static ca.uhn.fhir.empi.util.TestUtils.createDummyContext; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class PersonHelperDSTU3Test { + public static final FhirContext ourFhirContext = FhirContext.forDstu3(); + public static final String PATIENT_1 = "Patient/1"; + public static final String PATIENT_2 = "Patient/2"; + public static final String PATIENT_BAD = "Patient/BAD"; + public static final PersonHelper MY_PERSON_HELPER = new PersonHelper(ourFhirContext); + + @Test + public void testGetLinks() { + Person person = new Person(); + person.addLink().setTarget(new Reference(PATIENT_1)); + person.addLink().setTarget(new Reference(PATIENT_2)); + + { + List links = MY_PERSON_HELPER.getLinkIds(person).collect(Collectors.toList()); + assertEquals(2, links.size()); + assertEquals(PATIENT_1, links.get(0).getValue()); + assertEquals(PATIENT_2, links.get(1).getValue()); + assertTrue(MY_PERSON_HELPER.containsLinkTo(person, new IdDt(PATIENT_1))); + assertTrue(MY_PERSON_HELPER.containsLinkTo(person, new IdDt(PATIENT_2))); + assertFalse(MY_PERSON_HELPER.containsLinkTo(person, new IdDt(PATIENT_BAD))); + } + + { + MY_PERSON_HELPER.removeLink(person, new IdDt(PATIENT_1), createDummyContext()); + List links = MY_PERSON_HELPER.getLinkIds(person).collect(Collectors.toList()); + assertEquals(1, links.size()); + assertEquals(PATIENT_2, links.get(0).getValue()); + } + + + } + + @Test + public void testAddOrUpdateLinks() { + Person person = new Person(); + + //Links with no assurance level are rejected + { + MY_PERSON_HELPER.addOrUpdateLink(person, new IdDt(PATIENT_1), null, createDummyContext()); + assertThat(person.getLink().size(), is(equalTo(0))); + } + //Original link addition + { + MY_PERSON_HELPER.addOrUpdateLink(person, new IdDt(PATIENT_1), CanonicalIdentityAssuranceLevel.LEVEL3, createDummyContext()); + assertThat(person.getLink().size(), is(equalTo(1))); + } + + //Link update + { + MY_PERSON_HELPER.addOrUpdateLink(person, new IdDt(PATIENT_1), CanonicalIdentityAssuranceLevel.LEVEL4, createDummyContext()); + assertThat(person.getLink().size(), is(equalTo(1))); + } + + //New link + { + MY_PERSON_HELPER.addOrUpdateLink(person, new IdDt(PATIENT_2), CanonicalIdentityAssuranceLevel.LEVEL4, createDummyContext()); + assertThat(person.getLink().size(), is(equalTo(2))); + } + } + +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/PersonHelperR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/PersonHelperR4Test.java new file mode 100644 index 00000000000..1d02a88b916 --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/svc/PersonHelperR4Test.java @@ -0,0 +1,82 @@ +package ca.uhn.fhir.empi.svc; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel; +import ca.uhn.fhir.empi.util.PersonHelper; +import ca.uhn.fhir.model.primitive.IdDt; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Person; +import org.hl7.fhir.r4.model.Reference; +import org.junit.Test; + +import java.util.List; +import java.util.stream.Collectors; + +import static ca.uhn.fhir.empi.util.TestUtils.createDummyContext; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class PersonHelperR4Test { + public static final FhirContext ourFhirContext = FhirContext.forR4(); + public static final String PATIENT_1 = "Patient/1"; + public static final String PATIENT_2 = "Patient/2"; + public static final String PATIENT_BAD = "Patient/BAD"; + + public static final PersonHelper MY_PERSON_HELPER = new PersonHelper(ourFhirContext); + + @Test + public void testGetLinks() { + Person person = new Person(); + person.addLink().setTarget(new Reference(PATIENT_1)); + person.addLink().setTarget(new Reference(PATIENT_2)); + + { + List links = MY_PERSON_HELPER.getLinkIds(person).collect(Collectors.toList()); + assertEquals(2, links.size()); + assertEquals(PATIENT_1, links.get(0).getValue()); + assertEquals(PATIENT_2, links.get(1).getValue()); + assertTrue(MY_PERSON_HELPER.containsLinkTo(person, new IdDt(PATIENT_1))); + assertTrue(MY_PERSON_HELPER.containsLinkTo(person, new IdDt(PATIENT_2))); + assertFalse(MY_PERSON_HELPER.containsLinkTo(person, new IdDt(PATIENT_BAD))); + } + + { + MY_PERSON_HELPER.removeLink(person, new IdDt(PATIENT_1), createDummyContext()); + List links = MY_PERSON_HELPER.getLinkIds(person).collect(Collectors.toList()); + assertEquals(1, links.size()); + assertEquals(PATIENT_2, links.get(0).getValue()); + } + } + + @Test + public void testAddOrUpdateLinks() { + Person person = new Person(); + + //Link addition without assurance level should NOOP + { + MY_PERSON_HELPER.addOrUpdateLink(person, new IdDt(PATIENT_1), null, null); + assertThat(person.getLink().size(), is(equalTo(0))); + } + //Original link addition + { + MY_PERSON_HELPER.addOrUpdateLink(person, new IdDt(PATIENT_1), CanonicalIdentityAssuranceLevel.LEVEL3, createDummyContext()); + assertThat(person.getLink().size(), is(equalTo(1))); + } + + //Link update + { + MY_PERSON_HELPER.addOrUpdateLink(person, new IdDt(PATIENT_1), CanonicalIdentityAssuranceLevel.LEVEL4, createDummyContext()); + assertThat(person.getLink().size(), is(equalTo(1))); + } + + //New link + { + MY_PERSON_HELPER.addOrUpdateLink(person, new IdDt(PATIENT_2), CanonicalIdentityAssuranceLevel.LEVEL4, createDummyContext()); + assertThat(person.getLink().size(), is(equalTo(2))); + } + } +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/util/AssuranceLevelUtilTest.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/util/AssuranceLevelUtilTest.java new file mode 100644 index 00000000000..2d9d2c9830f --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/util/AssuranceLevelUtilTest.java @@ -0,0 +1,64 @@ +package ca.uhn.fhir.empi.util; + +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import junit.framework.TestCase; +import org.junit.Test; + +import static ca.uhn.fhir.empi.api.EmpiLinkSourceEnum.AUTO; +import static ca.uhn.fhir.empi.api.EmpiLinkSourceEnum.MANUAL; +import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.MATCH; +import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.NO_MATCH; +import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.POSSIBLE_DUPLICATE; +import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.POSSIBLE_MATCH; +import static ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel.LEVEL2; +import static ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel.LEVEL3; +import static ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel.LEVEL4; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; + +public class AssuranceLevelUtilTest extends TestCase { + + @Test + public void testValidPersonLinkLevels() { + assertThat(AssuranceLevelUtil.getAssuranceLevel(POSSIBLE_MATCH, AUTO), is(equalTo(LEVEL2))); + assertThat(AssuranceLevelUtil.getAssuranceLevel(MATCH, AUTO), is(equalTo(LEVEL3))); + assertThat(AssuranceLevelUtil.getAssuranceLevel(MATCH, MANUAL), is(equalTo(LEVEL4))); + + } + + @Test + public void testInvalidPersonLinkLevels() { + try { + AssuranceLevelUtil.getAssuranceLevel(NO_MATCH, AUTO); + fail(); + } catch (InvalidRequestException e) { + assertEquals("An AUTO EMPI Link may not have a match result of NO_MATCH", e.getMessage()); + } + try { + AssuranceLevelUtil.getAssuranceLevel(POSSIBLE_DUPLICATE, AUTO); + fail(); + } catch (InvalidRequestException e) { + assertEquals("An AUTO EMPI Link may not have a match result of POSSIBLE_DUPLICATE", e.getMessage()); + } + try { + AssuranceLevelUtil.getAssuranceLevel(NO_MATCH, MANUAL); + fail(); + } catch (InvalidRequestException e) { + assertEquals("A MANUAL EMPI Link may not have a match result of NO_MATCH", e.getMessage()); + } + try { + AssuranceLevelUtil.getAssuranceLevel(POSSIBLE_MATCH, MANUAL); + fail(); + } catch (InvalidRequestException e) { + assertEquals("A MANUAL EMPI Link may not have a match result of POSSIBLE_MATCH", e.getMessage()); + } + try { + AssuranceLevelUtil.getAssuranceLevel(POSSIBLE_DUPLICATE, MANUAL); + fail(); + } catch (InvalidRequestException e) { + assertEquals("A MANUAL EMPI Link may not have a match result of POSSIBLE_DUPLICATE", e.getMessage()); + } + } + +} diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/util/TestUtils.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/util/TestUtils.java new file mode 100644 index 00000000000..d70c9015609 --- /dev/null +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/util/TestUtils.java @@ -0,0 +1,10 @@ +package ca.uhn.fhir.empi.util; + +import ca.uhn.fhir.empi.model.EmpiTransactionContext; + +public class TestUtils { + public static EmpiTransactionContext createDummyContext() { + EmpiTransactionContext context = new EmpiTransactionContext(); + return context; + } +} diff --git a/hapi-fhir-server-empi/src/test/resources/bad-rules.json b/hapi-fhir-server-empi/src/test/resources/bad-rules.json new file mode 100644 index 00000000000..360c93908f5 --- /dev/null +++ b/hapi-fhir-server-empi/src/test/resources/bad-rules.json @@ -0,0 +1,16 @@ +{ + "candidateSearchParams" : [], + "candidateFilterSearchParams" : [], + "matchFields" : [ { + "name" : "given-name", + "resourceType" : "*", + "resourcePath" : "name.given", + "metric" : "COSINE", + "matchThreshold" : 0.8 + }], + "matchResultMap" : { + "given-name" : "POSSIBLE_MATCH", + "foo" : "MATCH" + }, + "eidSystem": "http://company.io/fhir/NamingSystem/custom-eid-system" +} diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java index 85f5cca6989..eff3882979e 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java @@ -23,7 +23,11 @@ import java.io.InputStream; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -73,6 +77,7 @@ public abstract class RequestDetails { private Map myUserData; private IBaseResource myResource; private String myRequestId; + private String myTransactionGuid; private String myFixedConditionalUrl; /** @@ -515,6 +520,14 @@ public abstract class RequestDetails { myDeferredInterceptorBroadcaster = null; } + public String getTransactionGuid() { + return myTransactionGuid; + } + + public void setTransactionGuid(String theTransactionGuid) { + myTransactionGuid = theTransactionGuid; + } + private class DeferredOperationCallback implements IInterceptorBroadcaster { diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/SimplePreResourceShowDetails.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/SimplePreResourceShowDetails.java index 3bc356d9d9c..85b10dc7ad2 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/SimplePreResourceShowDetails.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/SimplePreResourceShowDetails.java @@ -24,7 +24,6 @@ import com.google.common.collect.Lists; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index f1c66b0c7c4..1b2359f1192 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -590,7 +590,7 @@ public class RestfulServerUtils { if (theResource != null && isBlank(resName)) { FhirContext context = theServer.getFhirContext(); context = getContextForVersion(context, theResource.getStructureFhirVersionEnum()); - resName = context.getResourceDefinition(theResource).getName(); + resName = context.getResourceType(theResource); } if (isNotBlank(resName)) { retVal = theResourceId.withServerBase(theServerBase, resName); @@ -862,7 +862,7 @@ public class RestfulServerUtils { * parts, we're not streaming just the narrative as HTML (since bundles don't even * have one) */ - if ("Bundle".equals(theServer.getFhirContext().getResourceDefinition(theResource).getName())) { + if ("Bundle".equals(theServer.getFhirContext().getResourceType(theResource))) { encodingDomainResourceAsText = false; } } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java index 86f54c122fb..1844a8942b9 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java @@ -311,7 +311,7 @@ public interface IServerInterceptor { } public ActionRequestDetails(RequestDetails theRequestDetails, FhirContext theContext, IBaseResource theResource) { - this(theRequestDetails, theContext, theContext.getResourceDefinition(theResource).getName(), theResource.getIdElement()); + this(theRequestDetails, theContext, theContext.getResourceType(theResource), theResource.getIdElement()); myResource = theResource; } @@ -327,7 +327,7 @@ public interface IServerInterceptor { } public ActionRequestDetails(RequestDetails theRequestDetails, IBaseResource theResource) { - this(theRequestDetails, theRequestDetails.getServer().getFhirContext().getResourceDefinition(theResource).getName(), theResource.getIdElement()); + this(theRequestDetails, theRequestDetails.getServer().getFhirContext().getResourceType(theResource), theResource.getIdElement()); myResource = theResource; } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ServeMediaResourceRawInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ServeMediaResourceRawInterceptor.java index 7b0d3cb95a7..3d005af7740 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ServeMediaResourceRawInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ServeMediaResourceRawInterceptor.java @@ -74,7 +74,7 @@ public class ServeMediaResourceRawInterceptor { FhirContext context = theRequestDetails.getFhirContext(); - String resourceName = context.getResourceDefinition(theResponseObject).getName(); + String resourceName = context.getResourceType(theResponseObject); // Are we serving a FHIR read request on the Media resource type if (!"Media".equals(resourceName) || !RESPOND_TO_OPERATION_TYPES.contains(theRequestDetails.getRestOperationType())) { diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/OperationRule.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/OperationRule.java index 9ea039aa31c..6746e79f118 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/OperationRule.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/OperationRule.java @@ -113,7 +113,7 @@ class OperationRule extends BaseRule implements IAuthRule { } else if (myAppliesToTypes != null) { // TODO: Convert to a map of strings and keep the result for (Class next : myAppliesToTypes) { - String resName = ctx.getResourceDefinition(next).getName(); + String resName = ctx.getResourceType(next); if (resName.equals(theRequestDetails.getResourceName())) { applies = true; break; @@ -145,8 +145,8 @@ class OperationRule extends BaseRule implements IAuthRule { if (myAppliesToInstancesOfType != null) { // TODO: Convert to a map of strings and keep the result for (Class next : myAppliesToInstancesOfType) { - String resName = ctx.getResourceDefinition(next).getName(); - if (resName.equals(requestResourceId.getResourceType())) { + String resName = ctx.getResourceType(next); + if (resName.equals(requestResourceId .getResourceType())) { applies = true; break; } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplConditional.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplConditional.java index 1aca1599b45..8824b2cb195 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplConditional.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplConditional.java @@ -64,7 +64,7 @@ public class RuleImplConditional extends BaseRule implements IAuthRule { return null; } } else { - String inputResourceName = theRequestDetails.getFhirContext().getResourceDefinition(theInputResource).getName(); + String inputResourceName = theRequestDetails.getFhirContext().getResourceType(theInputResource); if (theInputResource == null || !myAppliesToTypes.contains(inputResourceName)) { return null; } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java index 5d8d30e91bb..ba24f2ce507 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java @@ -411,7 +411,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ { case TYPES: if (appliesToResource != null) { if (myClassifierType == ClassifierTypeEnum.ANY_ID) { - String type = theRequestDetails.getFhirContext().getResourceDefinition(appliesToResource).getName(); + String type = theRequestDetails.getFhirContext().getResourceType(appliesToResource); if (myAppliesToTypes.contains(type) == false) { return null; } @@ -555,7 +555,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ { } private boolean requestAppliesToTransaction(FhirContext theContext, RuleOpEnum theOp, IBaseResource theInputResource) { - if (!"Bundle".equals(theContext.getResourceDefinition(theInputResource).getName())) { + if (!"Bundle".equals(theContext.getResourceType(theInputResource))) { return false; } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseOutcomeReturningMethodBindingWithResourceParam.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseOutcomeReturningMethodBindingWithResourceParam.java index cec16b60d8c..06210b37058 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseOutcomeReturningMethodBindingWithResourceParam.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseOutcomeReturningMethodBindingWithResourceParam.java @@ -25,7 +25,6 @@ import static org.apache.commons.lang3.StringUtils.defaultIfBlank; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -79,7 +78,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu throw new ConfigurationException("Unable to determine resource type for method: " + theMethod); } - myResourceName = theContext.getResourceDefinition(myResourceType).getName(); + myResourceName = theContext.getResourceType(myResourceType); myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext()); if (myIdParamIndex != null) { myIdParamType = (Class) theMethod.getParameterTypes()[myIdParamIndex]; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/HistoryMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/HistoryMethodBinding.java index 9a92fc2e163..4353fe7a223 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/HistoryMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/HistoryMethodBinding.java @@ -80,7 +80,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { } if (type != IBaseResource.class && type != IResource.class) { - myResourceName = theContext.getResourceDefinition(type).getName(); + myResourceName = theContext.getResourceType(type); } else { myResourceName = null; } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java index ee5f810d031..8acde8fc02a 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java @@ -112,11 +112,11 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding { try { if (theReturnTypeFromRp != null) { - setResourceName(theContext.getResourceDefinition(theReturnTypeFromRp).getName()); + setResourceName(theContext.getResourceType(theReturnTypeFromRp)); } else if (Modifier.isAbstract(theOperationType.getModifiers()) == false) { - setResourceName(theContext.getResourceDefinition(theOperationType).getName()); + setResourceName(theContext.getResourceType(theOperationType)); } else if (isNotBlank(theOperationTypeName)) { - setResourceName(theContext.getResourceDefinition(theOperationTypeName).getName()); + setResourceName(theContext.getResourceType(theOperationTypeName)); } else { setResourceName(null); } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java index 7d54efd256d..e5b86f93806 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java @@ -99,7 +99,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { } if (theResourceProviderResourceType != null) { - this.myResourceProviderResourceName = theContext.getResourceDefinition(theResourceProviderResourceType).getName(); + this.myResourceProviderResourceName = theContext.getResourceType(theResourceProviderResourceType); } else { this.myResourceProviderResourceName = null; } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProvider.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProvider.java index 573beb3baf0..ce650300e58 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProvider.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/HashMapResourceProvider.java @@ -114,7 +114,7 @@ public class HashMapResourceProvider implements IResour public HashMapResourceProvider(FhirContext theFhirContext, Class theResourceType) { myFhirContext = theFhirContext; myResourceType = theResourceType; - myResourceName = myFhirContext.getResourceDefinition(theResourceType).getName(); + myResourceName = myFhirContext.getResourceType(theResourceType); clear(); } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/IResourceProviderFactoryObserver.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/IResourceProviderFactoryObserver.java new file mode 100644 index 00000000000..183db238a84 --- /dev/null +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/IResourceProviderFactoryObserver.java @@ -0,0 +1,28 @@ +package ca.uhn.fhir.rest.server.provider; + +/*- + * #%L + * HAPI FHIR - Server Framework + * %% + * Copyright (C) 2014 - 2020 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 javax.annotation.Nonnull; +import java.util.function.Supplier; + +public interface IResourceProviderFactoryObserver { + void update(@Nonnull Supplier theSupplier); +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/ProviderConstants.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java similarity index 63% rename from hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/ProviderConstants.java rename to hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java index c495ac5e307..7422faf51c3 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/ProviderConstants.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.model.util; +package ca.uhn.fhir.rest.server.provider; /*- * #%L - * HAPI FHIR Model + * HAPI FHIR - Server Framework * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -57,4 +57,27 @@ public class ProviderConstants { public static final String DIFF_FROM_PARAMETER = "from"; public static final String DIFF_TO_PARAMETER = "to"; public static final String DIFF_INCLUDE_META_PARAMETER = "includeMeta"; + + /** + * EMPI Operations + */ + public static final String EMPI_MATCH = "$match"; + public static final String EMPI_MATCH_RESOURCE = "resource"; + + public static final String EMPI_MERGE_PERSONS = "$empi-merge-persons"; + public static final String EMPI_MERGE_PERSONS_PERSON_ID_TO_DELETE = "personIdToDelete"; + public static final String EMPI_MERGE_PERSONS_PERSON_ID_TO_KEEP = "personIdToKeep"; + + public static final String EMPI_UPDATE_LINK = "$empi-update-link"; + public static final String EMPI_UPDATE_LINK_PERSON_ID = "personId"; + public static final String EMPI_UPDATE_LINK_TARGET_ID = "targetId"; + public static final String EMPI_UPDATE_LINK_MATCH_RESULT = "matchResult"; + + public static final String EMPI_QUERY_LINKS = "$empi-query-links"; + public static final String EMPI_QUERY_LINKS_PERSON_ID = "personId"; + public static final String EMPI_QUERY_LINKS_TARGET_ID = "targetId"; + public static final String EMPI_QUERY_LINKS_MATCH_RESULT = "matchResult"; + public static final String EMPI_QUERY_LINKS_LINK_SOURCE = "linkSource"; + + public static final String EMPI_DUPLICATE_PERSONS = "$empi-duplicate-persons"; } diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/rp/ResourceProviderFactory.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ResourceProviderFactory.java similarity index 69% rename from hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/rp/ResourceProviderFactory.java rename to hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ResourceProviderFactory.java index 5e43727ddb0..b575c55337f 100644 --- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/rp/ResourceProviderFactory.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ResourceProviderFactory.java @@ -1,8 +1,8 @@ -package ca.uhn.fhir.jpa.api.rp; +package ca.uhn.fhir.rest.server.provider; /*- * #%L - * HAPI FHIR JPA API + * HAPI FHIR - Server Framework * %% * Copyright (C) 2014 - 2020 University Health Network * %% @@ -22,15 +22,19 @@ package ca.uhn.fhir.jpa.api.rp; import javax.annotation.Nonnull; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.function.Supplier; public class ResourceProviderFactory { - + private Set myObservers = Collections.synchronizedSet(new HashSet<>()); private List> mySuppliers = new ArrayList<>(); public void addSupplier(@Nonnull Supplier theSupplier) { mySuppliers.add(theSupplier); + myObservers.forEach(observer -> observer.update(theSupplier)); } public List createProviders() { @@ -44,4 +48,11 @@ public class ResourceProviderFactory { return retVal; } + public void attach(IResourceProviderFactoryObserver theObserver) { + myObservers.add(theObserver); + } + + public void detach(IResourceProviderFactoryObserver theObserver) { + myObservers.remove(theObserver); + } } diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java index 3ee48e6052c..10b72a1e851 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java @@ -23,10 +23,10 @@ package ca.uhn.fhir.spring.boot.autoconfigure; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; import ca.uhn.fhir.jpa.config.BaseJavaConfigR4; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.provider.BaseJpaProvider; @@ -52,7 +52,12 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.*; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ResourceCondition; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; diff --git a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java index 9f059ebba7b..5519cbafc50 100644 --- a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java +++ b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java @@ -120,7 +120,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory { IdDt id = nextRes.getId(); if (id.hasResourceType() == false) { - String resName = myContext.getResourceDefinition(nextRes).getName(); + String resName = myContext.getResourceName(nextRes); id = id.withResourceType(resName); } @@ -316,7 +316,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory { IIdType id = nextRefRes.getIdElement(); if (id.hasResourceType() == false) { - String resName = myContext.getResourceDefinition(nextRefRes).getName(); + String resName = myContext.getResourceName(nextRefRes); id = id.withResourceType(resName); } diff --git a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/rest/server/Dstu2_1BundleFactory.java b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/rest/server/Dstu2_1BundleFactory.java index c45f1c0a274..f41f496de41 100644 --- a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/rest/server/Dstu2_1BundleFactory.java +++ b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/rest/server/Dstu2_1BundleFactory.java @@ -95,7 +95,7 @@ public class Dstu2_1BundleFactory implements IVersionSpecificBundleFactory { IIdType id = nextRes.getIdElement(); if (id.hasResourceType() == false) { - String resName = myContext.getResourceDefinition(nextRes).getName(); + String resName = myContext.getResourceType(nextRes); id = id.withResourceType(resName); } @@ -216,7 +216,7 @@ public class Dstu2_1BundleFactory implements IVersionSpecificBundleFactory { } else { if (isNotBlank(myBase) && next.getIdElement().hasIdPart()) { idElement = next.getIdElement(); - idElement = idElement.withServerBase(myBase, myContext.getResourceDefinition(next).getName()); + idElement = idElement.withServerBase(myBase, myContext.getResourceType(next)); entry.setFullUrl(idElement.toVersionless().getValue()); } } diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java index bec60461303..d9057aee39d 100644 --- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java +++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java @@ -28,11 +28,9 @@ import org.hl7.fhir.instance.model.api.*; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.api.BundleInclusionRule; import ca.uhn.fhir.model.api.*; -import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.model.dstu2.resource.Bundle.Link; -import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; import ca.uhn.fhir.model.dstu2.valueset.SearchEntryModeEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; @@ -92,7 +90,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory { IdDt id = nextRes.getId(); if (id.hasResourceType() == false) { - String resName = myContext.getResourceDefinition(nextRes).getName(); + String resName = myContext.getResourceType(nextRes); id = id.withResourceType(resName); } @@ -207,7 +205,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory { } else { if (isNotBlank(myBase) && next.getId().hasIdPart()) { IdDt id = next.getId().toVersionless(); - id = id.withServerBase(myBase, myContext.getResourceDefinition(next).getName()); + id = id.withServerBase(myBase, myContext.getResourceType(next)); entry.setFullUrl(id.getValue()); } } diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/Dstu3BundleFactory.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/Dstu3BundleFactory.java index 8858d27fa7e..4b2ee5e7200 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/Dstu3BundleFactory.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/Dstu3BundleFactory.java @@ -96,7 +96,7 @@ public class Dstu3BundleFactory implements IVersionSpecificBundleFactory { IIdType id = nextRes.getIdElement(); if (id.hasResourceType() == false) { - String resName = myContext.getResourceDefinition(nextRes).getName(); + String resName = myContext.getResourceType(nextRes); id = id.withResourceType(resName); } @@ -239,7 +239,7 @@ public class Dstu3BundleFactory implements IVersionSpecificBundleFactory { } else { if (isNotBlank(myBase) && next.getIdElement().hasIdPart()) { idElement = next.getIdElement(); - idElement = idElement.withServerBase(myBase, myContext.getResourceDefinition(next).getName()); + idElement = idElement.withServerBase(myBase, myContext.getResourceType(next)); entry.setFullUrl(idElement.toVersionless().getValue()); } } diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2hl7org/Dstu2Hl7OrgBundleFactory.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2hl7org/Dstu2Hl7OrgBundleFactory.java index 066b8326f00..c42b0573f45 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2hl7org/Dstu2Hl7OrgBundleFactory.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2hl7org/Dstu2Hl7OrgBundleFactory.java @@ -95,7 +95,7 @@ public class Dstu2Hl7OrgBundleFactory implements IVersionSpecificBundleFactory { IIdType id = nextRes.getIdElement(); if (id.hasResourceType() == false) { - String resName = myContext.getResourceDefinition(nextRes).getName(); + String resName = myContext.getResourceType(nextRes); id = id.withResourceType(resName); } @@ -242,7 +242,7 @@ public class Dstu2Hl7OrgBundleFactory implements IVersionSpecificBundleFactory { } else { if (isNotBlank(myBase) && next.getIdElement().hasIdPart()) { idElement = next.getIdElement(); - idElement = idElement.withServerBase(myBase, myContext.getResourceDefinition(next).getName()); + idElement = idElement.withServerBase(myBase, myContext.getResourceType(next)); entry.setFullUrl(idElement.toVersionless().getValue()); } } diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java index 21e25668efb..7952e4ffd15 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java @@ -31,7 +31,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -366,7 +365,7 @@ public final class HapiWorkerContext extends I18nBase implements IWorkerContext @Override public Set getResourceNamesAsSet() { - return myCtx.getResourceNames(); + return myCtx.getResourceTypes(); } @Override diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/rest/server/R4BundleFactory.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/rest/server/R4BundleFactory.java index 120352acb97..dfb264d14e3 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/rest/server/R4BundleFactory.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/rest/server/R4BundleFactory.java @@ -96,7 +96,7 @@ public class R4BundleFactory implements IVersionSpecificBundleFactory { IIdType id = nextRes.getIdElement(); if (id.hasResourceType() == false) { - String resName = myContext.getResourceDefinition(nextRes).getName(); + String resName = myContext.getResourceType(nextRes); id = id.withResourceType(resName); } @@ -241,7 +241,7 @@ public class R4BundleFactory implements IVersionSpecificBundleFactory { } else { if (isNotBlank(myBase) && next.getIdElement().hasIdPart()) { idElement = next.getIdElement(); - idElement = idElement.withServerBase(myBase, myContext.getResourceDefinition(next).getName()); + idElement = idElement.withServerBase(myBase, myContext.getResourceType(next)); entry.setFullUrl(idElement.toVersionless().getValue()); } } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java index 5dc23a10552..d04fc0a2c77 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java @@ -7,8 +7,8 @@ import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.parser.CustomTypeR4Test; import ca.uhn.fhir.parser.CustomTypeR4Test.MyCustomPatient; import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.api.DeleteCascadeModeEnum; import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.DeleteCascadeModeEnum; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.PreferReturnEnum; @@ -36,11 +36,20 @@ import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicStatusLine; -import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Binary; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.CapabilityStatement; +import org.hl7.fhir.r4.model.Device; +import org.hl7.fhir.r4.model.Encounter; +import org.hl7.fhir.r4.model.EpisodeOfCare; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Patient; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; @@ -51,7 +60,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; @@ -69,7 +77,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class GenericClientR4Test extends BaseGenericClientR4Test { diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ConsentInterceptorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ConsentInterceptorTest.java index c03761fe0c9..45c2b53a8a4 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ConsentInterceptorTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ConsentInterceptorTest.java @@ -30,26 +30,33 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.Patient; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.invocation.Invocation; import org.mockito.junit.MockitoJUnitRunner; import java.io.IOException; -import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; -import static org.awaitility.Awaitility.await; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.Matchers.not; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ConsentInterceptorTest { diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/ParametersUtilR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/ParametersUtilR4Test.java index bcb4cceb339..093699c8028 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/ParametersUtilR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/ParametersUtilR4Test.java @@ -1,8 +1,8 @@ package ca.uhn.fhir.util; import ca.uhn.fhir.context.FhirContext; -import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; +import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.r4.model.IntegerType; import org.hl7.fhir.r4.model.Parameters; @@ -12,20 +12,24 @@ import org.junit.Test; import java.util.List; import java.util.Optional; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class ParametersUtilR4Test { + private static final String TEST_PERSON_ID = "Person/32768"; + private static FhirContext ourFhirContext = FhirContext.forR4(); @Test public void testCreateParameters() { - FhirContext ctx = FhirContext.forR4(); - IBaseParameters parameters = ParametersUtil.newInstance(ctx); - ParametersUtil.addParameterToParameters(ctx, parameters, "someString", "string", "someStringValue"); - ParametersUtil.addParameterToParameters(ctx, parameters, "someDate", "date", "2019"); + IBaseParameters parameters = ParametersUtil.newInstance(ourFhirContext); + ParametersUtil.addParameterToParameters(ourFhirContext, parameters, "someString", "string", "someStringValue"); + ParametersUtil.addParameterToParameters(ourFhirContext, parameters, "someDate", "date", "2019"); - String encoded = ctx.newJsonParser().encodeResourceToString(parameters); + String encoded = ourFhirContext.newJsonParser().encodeResourceToString(parameters); assertEquals("{\"resourceType\":\"Parameters\",\"parameter\":[{\"name\":\"someString\",\"valueString\":\"someStringValue\"},{\"name\":\"someDate\",\"valueDate\":\"2019\"}]}", encoded); } @@ -47,7 +51,7 @@ public class ParametersUtilR4Test { .setValue(new StringType("VALUE4")); List values = ParametersUtil.getNamedParameterValuesAsString(FhirContext.forR4(), p, "foo"); - MatcherAssert.assertThat(values, Matchers.contains("VALUE1", "VALUE2")); + assertThat(values, Matchers.contains("VALUE1", "VALUE2")); } @Test @@ -62,4 +66,17 @@ public class ParametersUtilR4Test { assertEquals(123, value.get().intValue()); } + @Test + public void testGetNamedParameterPartAsString() { + IBaseParameters parameters = ParametersUtil.newInstance(ourFhirContext); + for (int i = 0; i < 3; ++i) { + IBase resultPart = ParametersUtil.addParameterToParameters(ourFhirContext, parameters, "link"); + ParametersUtil.addPartString(ourFhirContext, resultPart, "personId", TEST_PERSON_ID); + } + List values = ParametersUtil.getNamedParameterPartAsString(ourFhirContext, parameters, "link", "personId"); + assertThat(values, hasSize(3)); + assertThat(values.get(0), is(TEST_PERSON_ID)); + assertThat(values.get(1), is(TEST_PERSON_ID)); + assertThat(values.get(2), is(TEST_PERSON_ID)); + } } diff --git a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/HapiWorkerContext.java b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/HapiWorkerContext.java index 0fe8dfb1fae..04dc9c0e451 100644 --- a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/HapiWorkerContext.java +++ b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/HapiWorkerContext.java @@ -388,7 +388,7 @@ public final class HapiWorkerContext extends I18nBase implements IWorkerContext @Override public Set getResourceNamesAsSet() { - return myCtx.getResourceNames(); + return myCtx.getResourceTypes(); } @Override diff --git a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/rest/server/R5BundleFactory.java b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/rest/server/R5BundleFactory.java index a06d4f98cd3..42a3783f6a7 100644 --- a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/rest/server/R5BundleFactory.java +++ b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/rest/server/R5BundleFactory.java @@ -96,7 +96,7 @@ public class R5BundleFactory implements IVersionSpecificBundleFactory { IIdType id = nextRes.getIdElement(); if (id.hasResourceType() == false) { - String resName = myContext.getResourceDefinition(nextRes).getName(); + String resName = myContext.getResourceType(nextRes); id = id.withResourceType(resName); } @@ -242,7 +242,7 @@ public class R5BundleFactory implements IVersionSpecificBundleFactory { } else { if (isNotBlank(myBase) && next.getIdElement().hasIdPart()) { idElement = next.getIdElement(); - idElement = idElement.withServerBase(myBase, myContext.getResourceDefinition(next).getName()); + idElement = idElement.withServerBase(myBase, myContext.getResourceType(next)); entry.setFullUrl(idElement.toVersionless().getValue()); } } diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/test/concurrency/PointcutLatch.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/test/concurrency/PointcutLatch.java index af4e01ebac0..b1ef29ccbfa 100644 --- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/test/concurrency/PointcutLatch.java +++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/test/concurrency/PointcutLatch.java @@ -24,6 +24,7 @@ package ca.uhn.test.concurrency; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor; import ca.uhn.fhir.interceptor.api.Pointcut; +import com.google.common.collect.ListMultimap; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -223,6 +224,19 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch { return getLatchInvocationParameter(myCalledWith.get()); } + @SuppressWarnings("unchecked") + public T getLatchInvocationParameterOfType(Class theType) { + List hookParamsList = myCalledWith.get(); + Validate.notNull(hookParamsList); + Validate.isTrue(hookParamsList.size() == 1, "Expected Pointcut to be invoked 1 time"); + HookParams hookParams = hookParamsList.get(0); + ListMultimap, Object> paramsForType = hookParams.getParamsForType(); + List objects = paramsForType.get(theType); + Validate.isTrue(objects.size() == 1); + return (T) objects.get(0); + } + + private class PointcutLatchException extends IllegalStateException { private static final long serialVersionUID = 1372636272233536829L; diff --git a/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/FhirServerConfig.java b/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/FhirServerConfig.java index f2a193932e9..abed389fb35 100644 --- a/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/FhirServerConfig.java +++ b/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/FhirServerConfig.java @@ -3,11 +3,11 @@ package ca.uhn.fhir.jpa.test; //import javax.persistence.EntityManagerFactory; //import org.hibernate.jpa.HibernatePersistenceProvider; + +import ca.uhn.fhir.jpa.api.config.DaoConfig; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - import org.springframework.transaction.annotation.EnableTransactionManagement; - - import ca.uhn.fhir.jpa.api.config.DaoConfig; +import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @EnableTransactionManagement() diff --git a/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/OverlayTestApp.java b/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/OverlayTestApp.java index d039fe10247..b17eaa0eea3 100644 --- a/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/OverlayTestApp.java +++ b/hapi-fhir-testpage-overlay/src/test/java/ca/uhn/fhir/jpa/test/OverlayTestApp.java @@ -1,10 +1,19 @@ package ca.uhn.fhir.jpa.test; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.api.annotation.Description; +import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; +import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.rest.annotation.IncludeParam; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.TokenOrListParam; +import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.HandlerCollection; @@ -13,18 +22,10 @@ import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.webapp.WebAppContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.model.api.annotation.Description; -import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; -import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.TokenOrListParam; -import ca.uhn.fhir.rest.server.IResourceProvider; - import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; public class OverlayTestApp { diff --git a/hapi-fhir-testpage-overlay/src/test/resources/fhir-jpabase-spring-test-config.xml b/hapi-fhir-testpage-overlay/src/test/resources/fhir-jpabase-spring-test-config.xml index e28d7475d5e..8ebadf06b5c 100644 --- a/hapi-fhir-testpage-overlay/src/test/resources/fhir-jpabase-spring-test-config.xml +++ b/hapi-fhir-testpage-overlay/src/test/resources/fhir-jpabase-spring-test-config.xml @@ -1,14 +1,11 @@ - + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" + default-autowire="no" default-lazy-init="false"> diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/HapiToHl7OrgDstu2ValidatingSupportWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/HapiToHl7OrgDstu2ValidatingSupportWrapper.java index 26a005207e3..743d3dfaf1c 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/HapiToHl7OrgDstu2ValidatingSupportWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/HapiToHl7OrgDstu2ValidatingSupportWrapper.java @@ -61,7 +61,7 @@ public class HapiToHl7OrgDstu2ValidatingSupportWrapper extends BaseValidationSup } private Class translateTypeToHapi(Class theCodeSystemType) { - String resName = getFhirContext().getResourceDefinition(theCodeSystemType).getName(); + String resName = getFhirContext().getResourceType(theCodeSystemType); return myHapiCtx.getResourceDefinition(resName).getImplementingClass(); } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java index d86e67d51fa..6d15bb8fdfd 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java @@ -334,12 +334,12 @@ class VersionSpecificWorkerContextWrapper extends I18nBase implements IWorkerCon @Override public List getResourceNames() { - return new ArrayList<>(myValidationSupport.getFhirContext().getResourceNames()); + return new ArrayList<>(myValidationSupport.getFhirContext().getResourceTypes()); } @Override public Set getResourceNamesAsSet() { - return myValidationSupport.getFhirContext().getResourceNames(); + return myValidationSupport.getFhirContext().getResourceTypes(); } @Override diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm index 28195270c93..5f97b1253a7 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm @@ -23,7 +23,7 @@ import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.jpa.api.dao.*; import ca.uhn.fhir.jpa.dao.*; -import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; +import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; @Configuration public abstract class BaseJavaConfig${versionCapitalized} extends ca.uhn.fhir.jpa.config${package_suffix}.Base${versionCapitalized}Config { diff --git a/pom.xml b/pom.xml index 390b2e2a29a..18c7e55c9bd 100644 --- a/pom.xml +++ b/pom.xml @@ -2072,12 +2072,18 @@ + + + + + + @@ -2530,10 +2536,12 @@ hapi-fhir-base hapi-fhir-docs hapi-fhir-test-utilities + hapi-fhir-jpaserver-test-utilities hapi-tinder-plugin hapi-tinder-test hapi-fhir-client hapi-fhir-server + hapi-fhir-server-empi hapi-fhir-converter hapi-fhir-validation @@ -2556,6 +2564,7 @@ hapi-fhir-jaxrsserver-base hapi-fhir-jaxrsserver-example hapi-fhir-jpaserver-base + hapi-fhir-jpaserver-empi hapi-fhir-jpaserver-migrate restful-server-example hapi-fhir-testpage-overlay From 056721829d3d452b1e80f1c62d79f3bd1edd6e39 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Fri, 22 May 2020 15:56:14 -0400 Subject: [PATCH 2/2] License header updates --- .../jpa/empi/svc/EmpiEidUpdateService.java | 20 +++++++++++++++++++ .../channel/api/BaseChannelSettings.java | 20 +++++++++++++++++++ .../channel/api/IChannelSettings.java | 20 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiEidUpdateService.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiEidUpdateService.java index 3605d75b267..9ecfd1b5536 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiEidUpdateService.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiEidUpdateService.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.empi.svc; +/*- + * #%L + * HAPI FHIR JPA Server - Enterprise Master Patient Index + * %% + * Copyright (C) 2014 - 2020 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.empi.api.EmpiLinkSourceEnum; import ca.uhn.fhir.empi.api.EmpiMatchResultEnum; import ca.uhn.fhir.empi.api.IEmpiLinkSvc; diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/BaseChannelSettings.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/BaseChannelSettings.java index 417f5e3b83e..5f62b06becb 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/BaseChannelSettings.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/BaseChannelSettings.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.subscription.channel.api; +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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 BaseChannelSettings implements IChannelSettings { private boolean myQualifyChannelName = true; diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelSettings.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelSettings.java index a1563b38263..d90cc996003 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelSettings.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/IChannelSettings.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.subscription.channel.api; +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * Copyright (C) 2014 - 2020 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 interface IChannelSettings { boolean isQualifyChannelName(); }